Быстрая очистка памяти (Fill Zero)

Тема в разделе "WASM.BEGINNERS", создана пользователем Tronix, 3 окт 2010.

  1. Tronix

    Tronix Member

    Публикаций:
    0
    Регистрация:
    10 сен 2010
    Сообщения:
    122
    Доброго времени суток,

    Подскажите пожалуйста, как наиболее быстро очистить буфер (массив) с размером, кратным двум. Ну например софтовый видео-буфер (размером 1024 * 768 * 4 байт). Оговорюсь сразу, что это ассемблерные вставки в Delphi/Free Pascal. Я делаю по старинке, через rep stosd примерно так:
    Код (Text):
    1. mov edi,VidBuf
    2. mov ecx,(1024*768)/4
    3. xor eax,eax
    4. rep stosd
    но я тут подумал, ведь можно наверное как-то заюзать технологии типа MMX или SSE, что-бы сразу по 8 или 16 байт очищать.. Попробовал сделать на MMX что-то типа того:
    Код (Text):
    1.             mov edi,Mas
    2.             pxor mm0,mm0
    3.             mov ecx,(1024*768)/8
    4.          @fill:
    5.             movq [edi],mm0
    6.             add edi,8
    7.             loop @fill
    8.             emms
    Но получается плохо, скорость ниже чем у rep stosd. И попробовал через SSE вот так:
    Код (Text):
    1.    mov edi,Mas
    2.             xorps xmm0,xmm0
    3.             mov ecx,(1024*768)/16
    4.          @fill:
    5.             movntdq [edi],xmm0
    6.             add edi,16
    7.             loop @fill
    8.             emms
    Тут уже скорость наоборот приблизительно в 2 раза быстрее, чем rep stosd. Единственное, я так понимаю, что массив должен быть выравнен по 16-байтной границе, а в самом SSE нужно как-то тоже настраивать через prefetch. Вот с этим у меня реальная проблема, не понимаю я этих выравниваний (( Вот результаты в попугаях для вышеописанных тестов (очистка 128-ми мегабайтного буфера по 50 раз):
    Из чего следует, что SSE в данном случае рулит. Так-же странные результаты для MMX - я думал что он по-быстрее будет.

    Уважаемые спецы, подскажите пожалуйста, можно ли еще как-то убыстрить код? В частности что касается выравнивания для SSE... И нужно ли оно вообще, или языки высокого уровня типа Delphi сами все выравнивают?

    Ниже привожу код для Free Pascal v2.4.0 или Delphi v7, с помощью которого делал замеры:
    Код (Text):
    1. Uses Windows;
    2. Const
    3.       ArrSize  = 134217728;   //128Mb for test array
    4.       Cnt      = 50;          //tests count
    5. Var
    6.       Timer             : Cardinal;
    7.       PriorityClass,
    8.       Priority          : Cardinal;
    9.       Mas               : Pointer;
    10.       i                 : Cardinal;
    11.  
    12. Begin
    13.       GetMem(Mas,ArrSize); // Get 128Mb for array
    14.  
    15.       PriorityClass := GetPriorityClass(GetCurrentProcess);
    16.       Priority := GetThreadPriority(GetCurrentThread);
    17.       SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
    18.  
    19.       Timer := GetTickCount;
    20.       For i := 1 to Cnt do
    21.         FillChar(Mas^,ArrSize,0);
    22.  
    23.       WriteLn('FillChar() time: ',GetTickCount-Timer,' ms');
    24.  
    25.       Timer := GetTickCount;
    26.       For i := 1 to Cnt do
    27.       asm
    28.             mov edi,Mas
    29.             mov ecx,ArrSize/4
    30.             xor eax,eax
    31.             rep stosd
    32.       end;
    33.  
    34.       WriteLn('rep stosd time: ',GetTickCount-Timer,' ms');
    35.  
    36.       Timer := GetTickCount;
    37.       For i := 1 to Cnt do
    38.       asm
    39.             mov edi,Mas
    40.             pxor mm0,mm0
    41.             mov ecx,ArrSize/8
    42.          @fill:
    43.             movq [edi],mm0
    44.             add edi,8
    45.             loop @fill
    46.             emms
    47.       end;
    48.  
    49.       WriteLn('MMX fill: ',GetTickCount-Timer,' ms');
    50.  
    51.       Timer := GetTickCount;
    52.       For i := 1 to Cnt do
    53.       asm
    54.             mov edi,Mas
    55.             xorps xmm0,xmm0
    56.             mov ecx,ArrSize/16
    57.          @fill:
    58.             movntdq [edi],xmm0
    59.             add edi,16
    60.             loop @fill
    61.             emms
    62.       end;
    63.  
    64.       WriteLn('SSE fill: ',GetTickCount-Timer,' ms');
    65.  
    66.       SetThreadPriority(GetCurrentThread, Priority);
    67.       SetPriorityClass(GetCurrentProcess, PriorityClass);
    68.  
    69.       Write('all tests done. press ENTER to exit');
    70.       ReadLn;
    71.       FreeMem(Mas);
    72. End.
    Заранее спасибо за ответы.
     
  2. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    http://www.wasm.ru/forum/viewtopic.php?pid=287414#p287414
     
  3. Tronix

    Tronix Member

    Публикаций:
    0
    Регистрация:
    10 сен 2010
    Сообщения:
    122
    Y_Mur, спасибо, посмотрел вашу программу, та которая заполняет 100Mb массив вроде. Честно говоря потерялся в исходнике, некоторые вещи понятны, но больше непонятного. Еще несколько растерялся в результатах работы программы.
    [​IMG]
    Здесь получается, что быстрее работает заполнение прямым eax с prefetchw и заполнение xxm с prefetch0 (intel). В то же время скорость mmx и sse для AMD равны.

    В моем же тесте совершенно другие результаты, mmx работает дольше всех, sse же работает в два раза быстрее всех:
    Откуда же такая разница?
    Прикреплю скомпилированный мой тест и исходник на всякий случай:
     
  4. Tronix

    Tronix Member

    Публикаций:
    0
    Регистрация:
    10 сен 2010
    Сообщения:
    122
    Аааа, немного начинаю въезжать. Prefetch это же типа для того, чтобы в кеш положить что-то из памяти, пока мы что-то считаем инструкциями, так? Тогда получается, что он и не нужен в случае простого заполнения памяти нулями из mmx/sse регистра? Мы же не из памяти читаем значения...
     
  5. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    Tronix
    Самого быстрого варианта в той проге нет - он описан в той ветке чуть ниже в большом посте Leo ;)
     
  6. Asterix

    Asterix New Member

    Публикаций:
    0
    Регистрация:
    25 фев 2003
    Сообщения:
    3.576
    Tronix
    не заметил у вас выравнивания указателя
     
  7. Tronix

    Tronix Member

    Публикаций:
    0
    Регистрация:
    10 сен 2010
    Сообщения:
    122
    Уважаемый Asterix, а можно чуть-чуть поподробнее про выравнивание? Я вот посмотрел, в Free Pascal есть директива {$ALIGN 16}, но что с ней, что без нее - откомпилированные файлы различаются чуть меньше чем ничем. Во-вторых я же выделяю массив динамически, и куда его в память ложит GetMem - неведомо.. Как же тогда его выравнять? Юзать какие-то специальные API функции?

    Простите за наверное тупые вопросы...

    UPD: И еще, вот если в цикле MMX заменить movq на movntq, то скорость возрастает в два раза, но.. Я что-то не могу понять, movntq появилась где? В MMX или все-таки это уже целочисленный SSE? В одних манах одно, в других другое. На интеле вообще черт ногу сломит в разделе документации. Кому верить? ))
     
  8. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Tronix
    Наверное не те маны куришь ;) У интела в 1-м томе все по полочкам разложено, и соотв-но movntq\ps и prefetch относятся к Cacheability Control инструкциям SSE

    PS: Инструкция loop на всех современных компах довольно тормозная, поэтому вместо нее лучше юзать sub eсx,1 + jnz. Ну и разворот цикла на 2 или 4 тоже может дать несколько "копеек".
    Что касается выравнивания, то раз речь идет о достаточно больших блоках данных, то можно не полагаться на исходное выравнивание и кратность размера исходных данных, а просто сделать 3-х ступечатую обработку - сначала проверяем выравнивание исх.адреса и если он не выравнен на 8 или 16, то юзаем rep stosb для записи невыравненных байт, затем юзаем основной цикл на movntq или ..dq, и в самом конце еще раз rep stosb для оставшихся невыравненных байтов
     
  9. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    Tronix
    Посмотрел в отладчике твою GetMem - она базируется на HeapAlloc, прибаляя к возвращаемому ей адресу 30h (на какие-то служебные цели), так что буфер выравнен, но Leo прав - лучше на это не рассчитывать, а перепроверять :)
     
  10. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Это все - implementation specific и может зависеть как от компилятора, так и от размера выделяемого блока. Для гарантированного выравнивания нужно либо юзать VirtualAloc (которая кстати и автообнуление гарантирует), либо самомму проверять и смещать адрес после GetMem, либо затачиваться под конкретную версию компилятора (например, под Дельфи >= 2006 c FastMM менеджером памяти)
     
  11. Asterix

    Asterix New Member

    Публикаций:
    0
    Регистрация:
    25 фев 2003
    Сообщения:
    3.576
    Tronix
    Это директива для кода и данных в вашей программе, а вам нужно выравнивать указатель
    на память, который вернула винда при выделении памяти. Честно говоря я не помню должна
    ли винда при выделении памяти выровнять начало этой памяти на какую-то границу, наверно
    нет. Вот подумайте, вы собираетесь, например, читать блоками по 16 байт, а после выделения памяти имеете указатель типа 0x503044 ...
     
  12. Pavia

    Pavia Well-Known Member

    Публикаций:
    0
    Регистрация:
    17 июн 2003
    Сообщения:
    2.409
    Адрес:
    Fryazino
    Вот к примеру кусок из моего кода.
    Код (Text):
    1. const MyAlign=16
    2. ...
    3. LineLen:=NewWidth*SizeOf(Real)*FUnitFormat;
    4. LineLen:=(LineLen+MyAlign-1) and not (MyAlign-1);
    5.  
    6. GetMem(p,LineLen*NewHeight+MyAlign-1); // Выделяем чуть больше
    7. Map:=Pointer((Integer(p)+MyAlign-1) and not (MyAlign-1));// сдвигаем указатель на границу выравнивания
    Для освобождения сохранить p.
     
  13. Tronix

    Tronix Member

    Публикаций:
    0
    Регистрация:
    10 сен 2010
    Сообщения:
    122
    Разобрался. Всем спасибо за помощь.
    PS: А MMX, как оказалось, все равно тормоз. Что на Celeron 433 Mhz, что на современных процах.
     
  14. Asterix

    Asterix New Member

    Публикаций:
    0
    Регистрация:
    25 фев 2003
    Сообщения:
    3.576
    смотря с чем сравнивать