Как я могу обойти это исключение EOutOfMemory при кодировании очень большого файла? - PullRequest
4 голосов
/ 29 июня 2010

Я использую Delphi 2009 со строками Unicode.

Я пытаюсь закодировать очень большой файл, чтобы преобразовать его в Unicode:

var
  Buffer: TBytes;
  Value: string;

Value := Encoding.GetString(Buffer);

Это прекрасно работает для буфера размером 40 МБ, который удваивается в размере и возвращает значение в виде строки Unicode размером 80 МБ.

Когда я пытаюсь сделать это с буфером 300 МБ, это дает мне исключение EOutOfMemory.

Ну, это было не совсем неожиданно. Но я все равно решил проследить это.

Он входит в процедуру DynArraySetLength в системном блоке. В этой процедуре он переходит в кучу и вызывает ReallocMem. К моему удивлению, он успешно выделяет 665 124 864 байта !!!

Но затем в конце DynArraySetLength он вызывает FillChar:

  // Set the new memory to all zero bits
  FillChar((PAnsiChar(p) + elSize * oldLength)^, elSize * (newLength - oldLength), 0);

Вы можете увидеть по комментарию, что это должно делать. В этой подпрограмме немногое, но именно она вызывает исключение EOutOfMemory. Вот FillChar из системного блока:

procedure _FillChar(var Dest; count: Integer; Value: Char);
{$IFDEF PUREPASCAL}
var
  I: Integer;
  P: PAnsiChar;
begin
  P := PAnsiChar(@Dest);
  for I := count-1 downto 0 do
    P[I] := Value;
end;
{$ELSE}
asm                                  // Size = 153 Bytes
        CMP   EDX, 32
        MOV   CH, CL                 // Copy Value into both Bytes of CX
        JL    @@Small
        MOV   [EAX  ], CX            // Fill First 8 Bytes
        MOV   [EAX+2], CX
        MOV   [EAX+4], CX
        MOV   [EAX+6], CX
        SUB   EDX, 16
        FLD   QWORD PTR [EAX]
        FST   QWORD PTR [EAX+EDX]    // Fill Last 16 Bytes
        FST   QWORD PTR [EAX+EDX+8]
        MOV   ECX, EAX
        AND   ECX, 7                 // 8-Byte Align Writes
        SUB   ECX, 8
        SUB   EAX, ECX
        ADD   EDX, ECX
        ADD   EAX, EDX
        NEG   EDX
@@Loop:
        FST   QWORD PTR [EAX+EDX]    // Fill 16 Bytes per Loop
        FST   QWORD PTR [EAX+EDX+8]
        ADD   EDX, 16
        JL    @@Loop
        FFREE ST(0)
        FINCSTP
        RET
        NOP
        NOP
        NOP
@@Small:
        TEST  EDX, EDX
        JLE   @@Done
        MOV   [EAX+EDX-1], CL        // Fill Last Byte
        AND   EDX, -2                // No. of Words to Fill
        NEG   EDX
        LEA   EDX, [@@SmallFill + 60 + EDX * 2]
        JMP   EDX
        NOP                          // Align Jump Destinations
        NOP
@@SmallFill:
        MOV   [EAX+28], CX
        MOV   [EAX+26], CX
        MOV   [EAX+24], CX
        MOV   [EAX+22], CX
        MOV   [EAX+20], CX
        MOV   [EAX+18], CX
        MOV   [EAX+16], CX
        MOV   [EAX+14], CX
        MOV   [EAX+12], CX
        MOV   [EAX+10], CX
        MOV   [EAX+ 8], CX
        MOV   [EAX+ 6], CX
        MOV   [EAX+ 4], CX
        MOV   [EAX+ 2], CX
        MOV   [EAX   ], CX
        RET                          // DO NOT REMOVE - This is for Alignment
@@Done:
end;
{$ENDIF}

Итак, моя память была выделена, но она вылетала, пытаясь заполнить ее нулями. Это не имеет смысла для меня. Насколько я понимаю, память даже не должна быть заполнена нулями - и это, вероятно, в любом случае потеря времени - поскольку оператор Encoding собирается заполнить его в любом случае.

Могу ли я как-то помешать Delphi заполнить память?

Или есть какой-то другой способ, которым я могу заставить Delphi успешно распределить эту память для меня?

Моя настоящая цель - сделать это заявление о кодировке для моего очень большого файла, поэтому любое решение, которое позволит это сделать, будет высоко оценено.


Вывод: см. Мои комментарии к ответам.

Это предупреждение, чтобы быть осторожным при отладке ассемблерного кода. Убедитесь, что вы разбили все строки «RET», так как я пропустил одну в середине процедуры FillChar и ошибочно пришел к выводу, что FillChar вызвал проблему. Спасибо, Мейсон, за то, что указал на это.

Мне придется разбить входные данные на чанки для обработки очень большого файла.

Ответы [ 4 ]

6 голосов
/ 29 июня 2010

FillChar не выделяет память, так что это не ваша проблема. Попробуйте отследить его и установить точки останова в операторах RET, и вы увидите, что FillChar завершается. Какова бы ни была проблема, это, вероятно, на более позднем этапе.

5 голосов
/ 29 июня 2010

Чтение фрагмента из файла, кодирование и запись в другой файл, повтор.

1 голос
/ 29 июня 2010

Программы отлично зацикливаются.Они без устали зацикливаются.

Выделение огромного количества памяти занимает много времени.Будет много обращений к диспетчеру кучи.Ваша ОС даже не будет знать, есть ли у нее объем непрерывной памяти, который вам нужен заранее.Ваша ОС говорит, да, у меня есть 1 ГБ бесплатно.Но как только вы перейдете к его использованию, ваша ОС скажет: подождите, вы хотите все это одним куском?Позвольте мне убедиться, что мне достаточно всего в одном месте.Если это не так, вы получите сообщение об ошибке.

Если у него есть память, то для менеджера кучи еще много работы по подготовке памяти и маркировке ее как использованной.

Так что, очевидно, имеет смысл выделить меньше памяти и просто перебрать ее.Это избавляет компьютер от большой работы, которую он может отменить только после завершения работы.Почему бы не выполнить небольшую работу по выделению памяти, а затем просто продолжать ее использовать?

Память стека выделяется намного быстрее, чем память кучи.Если вы сохраняете небольшое использование памяти (по умолчанию менее 1 МБ), компилятор может просто использовать стековую память над кучей памяти, что сделает ваши циклы еще быстрее.Кроме того, локальные переменные, которые выделяются в регистре, очень быстрые.

Существуют такие факторы, как размер кластера жесткого диска и размер кэша, размер кэша ЦП и другие, которые дают подсказки о лучших размерах блоков.Ключ должен найти хороший номер.Мне нравится использовать куски размером 64 КБ.

1 голос
/ 29 июня 2010

Дикая догадка: может ли быть проблема в том, что память переполнена, и когда FillChar действительно получает доступ к памяти, он не может найти страницу, которая действительно дает вам?Я не знаю, будет ли Windows перегружать память, я знаю, что это делают некоторые ОС - вы не узнаете об этом, пока не попытаетесь использовать память.

Если этона случай, если это может вызвать взрыв в FillChar.

...