Нашел оптимальный способ бороться с пересечением кеш-линеек: Последние 4 байта текущей линейки и первые 4 следующей копируются в временную переменную (8 байт) однозначно ничего не пересекающую. При этом кстати автоматически производится префетч. В конце итерации используется обычное чтение из этой переменной. На копирование уходит по моим прикидкам 2 такта, тогда как чтение напрямую обходится в 10 и более штрафных такта (чтение 3-х значений со смежных линеек). Ну и практический результат: 620мб/с вместо 540мб/с. Файлик нового алгоритма прилагаю. [edited] В fscan.asm вкралась ошибка - параметры макросов не могут состоять из выражений, поэтом алгоритм проверял каждую ячейку по 4 раза. Вот исправленный участок: Код (Text): REPT 0Ch Compset a, N 1, 1 O = N + 10h Compset c, O, 1, 1 O = N + 20h Compset d, O, 1, 1 O = N + 30h Compset b, O, 1, 1 N = N + 1 ENDM Исправление хорошо сказалось на производительности, скорость поиска по максимуму достигала 645мб/с. Для сравнения: AMD Athlon XP 2000+ (1666): 422мб/с, разогнаный до 2600+ (2083):512мб/с (КТ400, DDR333), и Celeron 2400 после разгона до 3246: 840мб/с (Asus P4B533-X). Как видно код очень чувствителен к частоте камня. _2082663491__ffscan.zip
Переделал memcopy benchmark by S_T_A_S_, на макросы. Бесхитростная развертка цикла, работает чуть быстрее чем обычное mmx копирование (+3мб/с), но по сравнению с movntq тормозит. 191175025__memcopy2.Asm
>S_T_A_S_ Верно гнал по шине. Хотя в любом случае, я не ожидал такого ускорения. Да и от Атлона ожидал чуть побольше, все таки со сдвигами он получше справляется. Соотношения: Код (Text): AMD Athlon XP ext.clock 133, FSB266, cpu clock = 1667, scan 420 mb/s ext.clock 167, FSB333, cpu clock = 2083, scan 512 mb/s Intel Celeron ext.clock 100, FSB400, cpu = 2400, scan 645 mb/s ext.clock 133, FSB533, cpu = 3200, scan 840 mb/s В приближенных расчетах скорость пропорциональна частоте FSB, и частоте процессора.
Я бы сказал так: скорость пропорциональна частоте FSB и частота проца пропорциональна частоте FSB. т.е. имеем 2 величины (a и b), зависящие от FSB, говорить же, что a зависит от b imho не правильно. Что бы проверить, зависит ли скорость от частоты ядра, нужно 2 проца с разной частотой, но одинаковой FSB.
Да хорошо бы. Удалось еще ускорить после замены в макросах Код (Text): or ah, al shl eax, 1 на ror eax, 1 Эти замены для всех регистров позволяют более эффективно параллелить команды: cmp + ror, и setcc отдельно (не парится ?), ориентировочно 3 такта на эти 3 комманды. Алгоритм несколько поменялся, порядок битов теперь соответствует порядку байт. Еще избавился от двух операций условного перехода. На неотлаженном коде пока получены след. результаты: iCel2.4 @2405: 822 mb/sec iCel2.4 @3200: 1076 mb/sec AthlonX 2000+ @1667: 465 mb/sec Похоже на iCel, все таки частота камня что то значит. После отладки буду встраивать его в программу, ибо выжать чего либо еще уже почти невозможно. [edited] Delphi не выравнивает процедуры / функции даже по 8 байтной границе, производительность скачет, при изменении совершенно левого кода. Пришлось написать код для принудительного выравнивания, после чего на Athlone скорость достигла 476mb/sec. Нашел пример использования SMC на нужды оптимизации: http://algolist.manual.ru/graphics/3dfaq/articles/62.php _48185361__asm.zip
Скачал AMD CodeAnalyst 2.34 и не глядя натравил его на код. Среди тяжелых инструкций он нашел сначала только mov ebp, [esi + 40], которая обращается к данным вне кэша. Пришлось вынести весь prefetch во внешний цикл, от чего скорость возрасла с 476 до 553мб/c. В принципе неплохой результат, около 3 тактов на кажое проверенное смещение. Сейчас наибольшее торможение вызывает: первая инструкция записи результов mov [edi], ecx - 1,2% ее удельное время выполнения за одну итерацию. Существенно тормозит и чтение невыровненных данных попадающих в различные банки, каждая инструкция читающая их инструкция имеет около 1% удельного времени исполнения. При всем при этом 47% всего времени тратиться на prefetch цикл.
Session Type делал Timer Trigger ? Попробуй ещё Simulation - там картинки показывают, мне сильно помогли как-то.
>S_T_A_S_ Тулза требует файл отладочной информации .pdb для эмуляции конвеера. Delphi+Tasm такой файл сгенерить не могут, может есть конвертор? А так придется написать и на Visual Це++ тестовое приложение, и прилинковать к нем полученые objики, ради получения этого файла. Пока меня в устраивают текущие достижения. Проверка кода вынесеного prefetch на Селероне дала всего 530мб/с против 822мб/с. Так что с аппартным префетчем он луше работает, что в принципе и обьясняет его превосходство в 0.2% над Атлоном (на одинаковых частотах). Удалось выяснить что на Атлоне торомозит лишь первая инструкция чтения данных попадающих сразу в два банка, а остальные две проходят нормально. Выравнивание кода тоже оказывает сильное влияние при выполнении его на Атлоне. [edited] Запуск на Селероне CA показал, что самая тяжелая инструкция все-таки та что обращается к памяти отсутствующей в кэше. Торможение за счет внещнего префетча, на самом деле объясняется малым размером кэша L1 у Селерона, по сравнению с размером блока подгружаемых данных (64К-64), при этом кэш L2 его не спасает. Если обратно вставить условные переходы в участке RLE упаковки скорость возрастает до 953мб/с. Однако когда на входе будут реальные данные, ошибочные предсказания переходов скорость сильно снизят, что я конечно проверю.
alpet > Конвертера нет. Есть скрипт, частично решающий задачу. Не для всех случаев, но иногда помогает.
Попробую конвертер, но в любом случае буду искать/писать тулзу для извлечения/конвертации отладочной инфы. Пока же я попробывал этот код прицепить к приложению Це. Удалось его пропустить через AMD CodeAnalyst, результаты меня несколько удивили сначала: инструкции на декодировку лезут по три штуки, и выполнение 3 комманд укладывается в один такт. Missaling добавляет по одному такту на комманду. Но самое главное это delta, для набора одинаково тяжелых инструкций он равен 1, и время выполнения похоже 1 такт. На проверку 64 смещений (одна итерация) в среднем уходит 109 тактов. Код (Text): ... Единственно жаль, что эмулятор CA не делает ассимиляцию результатов всех итераций цикла, в одну. Таким образом первая итерация выглядит жутковато, и судить можно лишь по последующим. В целом, больше оптимизировать код не удалось, на картинке эмуляции он выглядит идеально. Вызывает удивление слишком долгое выполнение комманды записи в память (как SMC, так и самые первые записи результатов), я предполагал что запись идет в буффер, и в случае отсутствия в кэше, нужной линейки, продгрузка идет без штрафных тактов (если конечно значения из этой линейки не потребуются сразу). Поскольку запись оказывает сильное влияние (например если при каждой итерации, будут получаться разные множества, и их каждое записывать - скорость поиска упадет с 593 до 480мб/с), пришлось оставить условные переходы. Возник вопрос: просматривая manual CA, я начал понимать что Event Trigger, работает через performance counters проца. Кто нибудь пытался их использовать (кроме простого замера времени исполнения), и что из этого вышло?
alpet > Попробуй использовать запись через MMX регистры минуя кэш - MOVNTQ. Иногда даёт неплохой прирост в скорости.
2 S_T_A_S_ Я бы с удовольствием - да в большинстве случаев это дело мне нужно в кэше (на крайняк уровня L2). В данном случае проблема не в записи, а в отсутствии необходимой линейки в обоих кэшах, попробую ее префетчить в L2. В худшем случае для 64К смещений получится 16Кбайт результов. Да и потом код при всех достоинствах обязан таки быть совместим с машинами класса PII. В любом случае весь внешний код роняет прозводительность настолько сильно, что оптимизацией именно этого алгоритма дальше просто бессмысленно. Вчера проверял внутри WGC его - 166 мб/с в обычном режиме (чтение через ReadProcessMemory), и 370 мб/с в Spy-Mode. Может стоит использовать ReadProcessMemory с блоками размером не более чем L1Cache.Size, как-никак она дважды копирует блок памяти с помощью rep movsd, сначала в пространство в ядра, и только потом в пространство процесса? [edited] Настроил размер буффера в L2Cache.Size / 2, и скорость поиска возросла до 220мб/с. На Селероне 2.4 при этом недостичь скорости более 180мб/с - сказывается малый объем кэша. Код (Text): ... Могут ли три практически подряд идущих в конце каждой итерации инструкции условного перехода, вызывать постоянную ошибку предсказания?
Выпустил таки релиз программы. Всем желающим проверить даю линк: http://www.alpet.hotmail.ru/wgc240.zip Результат весьма хороший, но я планирую его улучшить. Поискал инфу по PDB файлам, но полезного нашел мало: http://www.informit.com/articles/article.asp?p=22685&redir=1 http://www.ecn.purdue.edu/~laird/WINE/debugger/msc.c Код (Text): ... Вопрос: если использовать обычное копирование в неkэшируемую память (аттрибуты страниц которых настроенны соответствующим образом), можно ли будет добиться производительности сходной с копированием коммандой movntq +sfence ? Ведь копирование в видеопамять таким образом осуществляется.
Провел еще мало-мальскую оптимизацию. Теперь эффективность поиска достигла порядка 1,6 тактов на проверку одного смещения (включая не выровненные). По моему больше производительность поднять не удастся. Исходники к программе я открыл для всех кого не отпугивает код на Delphi и TASM. Тут кстати возник вопрос: FASM может генерировать объектники совместимые с Delphi ? Заодно реализовал возможность проверки только выровненных значений: скорость при сканировании в кэше L1 на AMD2000XP - 4200 МиБ/сек, а в памяти получается около 760 МиБ/сек. На iCell2400 (+AI Overclk Tuner - на частоту можно не надеятся - вполне может достигать и 3500) этот поиск достигает 1030 МиБ/сек. МиБ - мебибайт = 1048576 байт, как я узнал оказывается мегабайт на самом деле равен 1000000 байт, причем уже с 1997 года Теперь буду писать отдельный многопоточный менеджер памяти с использованием SMC при необходимости, больше оптимизировать практически нечего.
Решился таки переписать алгоритмы и на MMX. Очень не плохой результат получился пока для поиска DWORD значений. Вот с байтами пока проблема. Алгоритм должен получать на входе 64 байта, и выдавать 2 множества по 32 бита, где единичные биты соответствуют позициям совпадений. Вот что будет использоваться пока: Код (Text): // File fscaner.cpp #include "stdafx.h" #include "windows.h" typedef unsigned __int64 QWORD; const QWORD mask = 0x0180018001800180; // 4 битовые пары const QWORD mask2 = 0xFFFF; // 16 lower bits int _tmain(int argc, _TCHAR* argv[]) { QWORD example = 0; // find zeros DWORD bigSize = 64 * 1024 * 1024; PVOID psrc = VirtualAlloc (NULL, bigSize, MEM_COMMIT, PAGE_READWRITE); if (!psrc) return GetLastError (); ZeroMemory (psrc, bigSize); // for scan zeros DWORD ticks = GetTickCount (); _asm { push eax push ecx push edx push ebx push esi push edi mov esi, psrc mov edi, bigSize add edi, esi // limit movq mm6, example // 8 копий образца movq mm5, mask // Для выделения битовых пар loop_big: mov ebx, 20h pxor mm4, mm4 loop_small: // 16 bytes test per cycle psllq mm4, 16 // to high part movq mm0, [esi + ebx + 18h] movq mm1, [esi + ebx - 08h] pcmpeqd mm0, mm6 pcmpeqd mm1, mm6 pand mm0, mm5 pand mm1, mm5 movq mm2, mm0 movq mm3, mm1 psrlq mm0, 30 // result in low part psrlq mm1, 30 // result in high part por mm0, mm2 por mm1, mm3 punpckldq mm0, mm1 // 2 low 32bit parts to one 64bit psrlq mm0, 7 // now is normal movq mm1, mm0 psrlq mm0, 28 por mm0, mm1 punpcklbw mm0, mm0 // pack and complete pand mm0, mask2 por mm4, mm0 sub ebx, 8 jnz loop_small add esi, 40h cmp esi, edi jb loop_big pop edi pop esi pop ebx pop edx pop ecx pop eax emms } ticks = GetTickCount () - ticks; double speed = 1000 * 64 / ticks; // mb / sec char tmp [128]; sprintf (tmp, "Scan speed ~= %.2f mb/sec\n", speed); OutputDebugString (tmp); VirtualFree (psrc, 0, MEM_RELEASE); // release region return 0; } Но мне не нравится слишком большое количество зависимостей. Может упаковка восьмибайтного множества (для каждого элемента один байт) в восьмибитное и проще как осуществляется? Алгоритм кстати можно будет запользовать и для поиска строк более 8 байт длинной, если получится достаточно быстрым. [edited] В аттаче алгоритм поиска DWORDов. Правда еще не оптимизированный, но уже рабочий.
Дома добился лучших результатов сканирования - порядка 900мб/сек на AMD AthlonXP 2000+, что очень не плохо для невыровненных значений. Скажем iCell@2400 проигрывает на MMX алгоритме, выдавая около 800мб/сек, хотя это может быть и следствием не правильно рассчитаной дистанции предвыборки (w/prefetchnta). Старый алгоритм для FASM 709269434__memread2.asm