Добавлен , опубликован
Статья
Раздел:
Разное
Когда у игры неполучается выделить память, она показывает сообщение об ошибке "Недостаточно памяти для обработки команды".
Произойти это может, если память доступная процессу игры кончилась.
Даже если у вас 64-битный процессор и десятки гигабайт оперативной памяти, игра 32-битная и не может использовать больше 4-ех гигабайт памяти.
А на практике, даже больше 2-ух гигабайт будет проблемно занять, из-за особености устройства используемых варкрафтом связных списков.
Также выделение может провалиться даже когда еще много свободной памяти.
Дело в том, что даже если сумарное количество свободной памяти достаточно велико, свободного пространства с одним цельным блоком нужного размера может всё равно не найтись.

Регионы памяти

В конце отчета об ошибке, создаваемого игрой при краше от нехватки памяти, можно увидеть таблицу регионов памяти.
Пример таблицы
   12  2784 e:\Drive1\temp\buildwar3x\War3\Source\UI/CCommandButton.cpp(546)
    1   528 .\FrameDef.cpp(69)
   12   768 e:\drive1\temp\buildwar3x\war3\source\game\CFogMaskTable.h(98)
    1    56 .\CGameUI.cpp(517)
    1   512 e:\Drive1\temp\buildwar3x\War3\Source\WorldEdit/WEUtilities.cpp(3422)
  286 12308 .\W32\OsISndCache.cpp(991)
  143  4004 .?AUNATIVETOKEN@@(-2)
...
Первая колонка содержит количество выделенных блоков памяти, а вторая их сумарный размер.
Дальше идет либо имя исходного файла и номер строки, либо закодированое имя типа и отрицательное число.
По сигнатуре региона можно попытаться догадаться о причине произошедшего.
Например, если имя объекта CUnitListNode, то можно заподозрить утечки памяти, связанные с неудаляемыми групами в скрипте карты.

Читаемая таблица регионов

Для более удобного просмотра таблицы, предлагаю воспользоваться моим скриптом на питоне, который сортирует элементы таблицы и конвертирует их в более читаемый вид.
Установите python последнией версии (3.x), но может быть и на старых заработает.
wc3memsort.py
import sys
import re
from contextlib import nullcontext
from collections import namedtuple

def open_input(path):
	return open(path, 'rt') if path != '-' else nullcontext(sys.stdin)

def open_output(path):
	return open(path, 'wt') if path != '-' else nullcontext(sys.stdout)

input_path = sys.argv[1]
output_path = sys.argv[2]

with open_input(input_path) as f:
	input_data = f.readlines()

pattern = re.compile(r' *([0-9]+) +([0-9]+) +(.*)\((-?[0-9]+)\)')

MemoryRegion = namedtuple('MemoryRegion', ['blocks_count', 'size', 'source_file', 'source_line'])

def parse_line(line):
	m = pattern.match(line)
	if m:
		return MemoryRegion(
			blocks_count = int(m.group(1)),
			size = int(m.group(2)),
			source_file = m.group(3),
			source_line = int(m.group(4)),
		)

def parse_lines(lines):
	for i, line in enumerate(lines):
		parsed_line = parse_line(line)
		if parsed_line:
			yield parsed_line
		else:
			raise Exception(f'Failed to convert line number {i+1}.')
			
memory_regions = list(parse_lines(input_data))
total_size = sum(r.size for r in memory_regions)
sorted_memory_regions = sorted(memory_regions, reverse=True, key=lambda r: r.size)
formatted_memory_regions = (format_memory_region(r) for r in sorted_memory_regions)

def format_size(size):
	suffixes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB']

	i = 0
	while size >= 1024:
		size /= 1024
		i += 1

	if i >= len(suffixes):
		i = len(suffixes) - 1

	if i > 0:
		return f'{size:.2f} {suffixes[i]}'
	else:
		return f'{size} {suffixes[i]}'

def format_memory_region(r):
	pretty_size = format_size(r.size)
	return f'{pretty_size}\t{r.blocks_count}\t{r.source_file}\t{r.source_line}'

with open_output(output_path) as f:
	f.write(f'total size: {format_size(total_size)}.\n')
	f.write('\n'.join(formatted_memory_regions))
	f.write('\n')
Применение:
python wc3memsort.py <путь к файлу с исходной таблицей> <путь к файлу для записи результата>
Вместо пути может быть указан минус "-", в таком случае для чтения или записи будет использоваться стандартный ввод/вывод.
На вход скрипт принимает не весь отчет об ошибке, а лишь фрагмент с таблицей находящийся в конце файла между разделяющими полосами.
На выходе пишется сначала сумарное количество памяти, занимаемое всеми регионами памяти, а затем таблица регионов, в которой первая колонка содержит размер региона памяти, а вторая количество блоков памяти. Дальше, как и в оригинале, идет сигнатура региона.
Пример результата:
total size: 670.44 MiB.
145.41 MiB	1584	.?AVCAgentBaseAbs@@	-2
62.51 MiB	95047	.\cmemblock.cpp	372
59.05 MiB	40199	.?AVC3Vector@NTempest@@	-2
51.49 MiB	1468	.?AUCustomObjectField@@	-2
51.28 MiB	729588	.\CDataAllocator.cpp	152
...
`
ОЖИДАНИЕ РЕКЛАМЫ...