Доброго времени суток, Подскажите пожалуйста, как наиболее быстро очистить буфер (массив) с размером, кратным двум. Ну например софтовый видео-буфер (размером 1024 * 768 * 4 байт). Оговорюсь сразу, что это ассемблерные вставки в Delphi/Free Pascal. Я делаю по старинке, через rep stosd примерно так: Код (Text): mov edi,VidBuf mov ecx,(1024*768)/4 xor eax,eax rep stosd но я тут подумал, ведь можно наверное как-то заюзать технологии типа MMX или SSE, что-бы сразу по 8 или 16 байт очищать.. Попробовал сделать на MMX что-то типа того: Код (Text): mov edi,Mas pxor mm0,mm0 mov ecx,(1024*768)/8 @fill: movq [edi],mm0 add edi,8 loop @fill emms Но получается плохо, скорость ниже чем у rep stosd. И попробовал через SSE вот так: Код (Text): mov edi,Mas xorps xmm0,xmm0 mov ecx,(1024*768)/16 @fill: movntdq [edi],xmm0 add edi,16 loop @fill emms Тут уже скорость наоборот приблизительно в 2 раза быстрее, чем rep stosd. Единственное, я так понимаю, что массив должен быть выравнен по 16-байтной границе, а в самом SSE нужно как-то тоже настраивать через prefetch. Вот с этим у меня реальная проблема, не понимаю я этих выравниваний (( Вот результаты в попугаях для вышеописанных тестов (очистка 128-ми мегабайтного буфера по 50 раз): Из чего следует, что SSE в данном случае рулит. Так-же странные результаты для MMX - я думал что он по-быстрее будет. Уважаемые спецы, подскажите пожалуйста, можно ли еще как-то убыстрить код? В частности что касается выравнивания для SSE... И нужно ли оно вообще, или языки высокого уровня типа Delphi сами все выравнивают? Ниже привожу код для Free Pascal v2.4.0 или Delphi v7, с помощью которого делал замеры: Код (Text): Uses Windows; Const ArrSize = 134217728; //128Mb for test array Cnt = 50; //tests count Var Timer : Cardinal; PriorityClass, Priority : Cardinal; Mas : Pointer; i : Cardinal; Begin GetMem(Mas,ArrSize); // Get 128Mb for array PriorityClass := GetPriorityClass(GetCurrentProcess); Priority := GetThreadPriority(GetCurrentThread); SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL); Timer := GetTickCount; For i := 1 to Cnt do FillChar(Mas^,ArrSize,0); WriteLn('FillChar() time: ',GetTickCount-Timer,' ms'); Timer := GetTickCount; For i := 1 to Cnt do asm mov edi,Mas mov ecx,ArrSize/4 xor eax,eax rep stosd end; WriteLn('rep stosd time: ',GetTickCount-Timer,' ms'); Timer := GetTickCount; For i := 1 to Cnt do asm mov edi,Mas pxor mm0,mm0 mov ecx,ArrSize/8 @fill: movq [edi],mm0 add edi,8 loop @fill emms end; WriteLn('MMX fill: ',GetTickCount-Timer,' ms'); Timer := GetTickCount; For i := 1 to Cnt do asm mov edi,Mas xorps xmm0,xmm0 mov ecx,ArrSize/16 @fill: movntdq [edi],xmm0 add edi,16 loop @fill emms end; WriteLn('SSE fill: ',GetTickCount-Timer,' ms'); SetThreadPriority(GetCurrentThread, Priority); SetPriorityClass(GetCurrentProcess, PriorityClass); Write('all tests done. press ENTER to exit'); ReadLn; FreeMem(Mas); End. Заранее спасибо за ответы.
Y_Mur, спасибо, посмотрел вашу программу, та которая заполняет 100Mb массив вроде. Честно говоря потерялся в исходнике, некоторые вещи понятны, но больше непонятного. Еще несколько растерялся в результатах работы программы. Здесь получается, что быстрее работает заполнение прямым eax с prefetchw и заполнение xxm с prefetch0 (intel). В то же время скорость mmx и sse для AMD равны. В моем же тесте совершенно другие результаты, mmx работает дольше всех, sse же работает в два раза быстрее всех: Откуда же такая разница? Прикреплю скомпилированный мой тест и исходник на всякий случай:
Аааа, немного начинаю въезжать. Prefetch это же типа для того, чтобы в кеш положить что-то из памяти, пока мы что-то считаем инструкциями, так? Тогда получается, что он и не нужен в случае простого заполнения памяти нулями из mmx/sse регистра? Мы же не из памяти читаем значения...
Tronix Самого быстрого варианта в той проге нет - он описан в той ветке чуть ниже в большом посте Leo
Уважаемый Asterix, а можно чуть-чуть поподробнее про выравнивание? Я вот посмотрел, в Free Pascal есть директива {$ALIGN 16}, но что с ней, что без нее - откомпилированные файлы различаются чуть меньше чем ничем. Во-вторых я же выделяю массив динамически, и куда его в память ложит GetMem - неведомо.. Как же тогда его выравнять? Юзать какие-то специальные API функции? Простите за наверное тупые вопросы... UPD: И еще, вот если в цикле MMX заменить movq на movntq, то скорость возрастает в два раза, но.. Я что-то не могу понять, movntq появилась где? В MMX или все-таки это уже целочисленный SSE? В одних манах одно, в других другое. На интеле вообще черт ногу сломит в разделе документации. Кому верить? ))
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 для оставшихся невыравненных байтов
Tronix Посмотрел в отладчике твою GetMem - она базируется на HeapAlloc, прибаляя к возвращаемому ей адресу 30h (на какие-то служебные цели), так что буфер выравнен, но Leo прав - лучше на это не рассчитывать, а перепроверять
Это все - implementation specific и может зависеть как от компилятора, так и от размера выделяемого блока. Для гарантированного выравнивания нужно либо юзать VirtualAloc (которая кстати и автообнуление гарантирует), либо самомму проверять и смещать адрес после GetMem, либо затачиваться под конкретную версию компилятора (например, под Дельфи >= 2006 c FastMM менеджером памяти)
Tronix Это директива для кода и данных в вашей программе, а вам нужно выравнивать указатель на память, который вернула винда при выделении памяти. Честно говоря я не помню должна ли винда при выделении памяти выровнять начало этой памяти на какую-то границу, наверно нет. Вот подумайте, вы собираетесь, например, читать блоками по 16 байт, а после выделения памяти имеете указатель типа 0x503044 ...
Вот к примеру кусок из моего кода. Код (Text): const MyAlign=16 ... LineLen:=NewWidth*SizeOf(Real)*FUnitFormat; LineLen:=(LineLen+MyAlign-1) and not (MyAlign-1); GetMem(p,LineLen*NewHeight+MyAlign-1); // Выделяем чуть больше Map:=Pointer((Integer(p)+MyAlign-1) and not (MyAlign-1));// сдвигаем указатель на границу выравнивания Для освобождения сохранить p.
Разобрался. Всем спасибо за помощь. PS: А MMX, как оказалось, все равно тормоз. Что на Celeron 433 Mhz, что на современных процах.