Здравствуйте, OpenGL, Вы писали:
OGL>mmx,(3dnow, 3dnow+) sse, sse2 самое быстрое последнее 128 бит за цикл.
не возможности протестировать sse2, но разницы между обнулением через mmx(64bit) и xmm(128bit) нет ни какой. Это протестировано
Здравствуйте, zix, Вы писали:
zix>Здравствуйте, Alfarn, Вы писали:
A>>Есть массив DWORD'ов. Нужна максимально быстрая процедура для его обнуления. memset не предлагать!
zix>Я думаю, быстрее чем команда STOS ничего найти не удастся...
Крис Касперски описывал алгоритм, который позволяет сделать функции memset,memcpy быстрее. Для этого надо знать про кэши. Суть в том, чтобы периодически обращаться к памяти, которая будет обрабатываться позже, а затем продолжать операции.
В итоге к тому моменту когда очередь дойдет до той памяти, её содержимое уже будет в кэше.
Несколько стратегий (можно комбинировать):
1) находим точку вычищаемого блока, начинающуюся на границе N байт (где N — сколько байт будем чистить одной инструкцией), и чистим начиная с нее (т.е. соблюдая выравнивание); остальное дотираем вручную.
2) возможно, поможет TLB priming. Вообще говоря, этот прием предназначен скорее для чтения данных, но, может статься, ускорит и запись. Речь идет о том, чтобы каждый раз перед переходом записи на границу новой страницы (4К) заранее обращаться к одному элементу на этой странице, чтобы ее данные успели подгрузиться в TLB (Transaction Lookaside Buffer).
3) ну и, конечно, если доступен набор команд MMX, можно юзать MMX-инструкции, а еще лучше SIMD 2. На P4 инструкция MOVAPS ворочает по 128 бит за одну операцию. Если так роскошествовать не получается, можно пользоваться MOVQ. Между прочим, MOVAPS требует 16-байтного выравнивания точки записи.
Кроме того, есть еще как минимум 2 интересных подхода.
I) воспользоваться страничной адресацией: замапить на весь требуемый диапазон логического адресного пространства одну и ту же (или несколько) страницу памяти, которую предварительно зануляем. Есть некоторые шансы (не проверял), что VirtualAlloc, например, 32 страниц будет работать быстрее, чем заполнение нулями 32*4=128 Кб данных. Режим защиты можно указать PAGE_WRITECOPY — тогда при любых изменениях этой области памяти будут создаваться новые физические страницы.
II) пропатчить процесс, обрабатывающий данные, так, чтобы он их вообще не читал, а сразу полагал равными 0. По окончании нужной операции можно пропатчить обратно. Проблема, которая видна сразу же — если в том процессе работает несколько потоков, одновременно обрабатывающих одним кодом разные области данных, будет плохо.
Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Здравствуйте, Alfarn, Вы писали:
A>Есть массив DWORD'ов. A>Нужна максимально быстрая процедура для его обнуления. memset не предлагать!
Думаю, альтернативы для memset (или его клонов) просто нет.
Что касается ассемблера, то самая быстрая получается инструкция movq.
В принципе, то как следует работать с кэшем вот здесь.
Пример, правда, для AMD и для memcpy. Но я переделывал его для memset и для memcmp.
Работает и на Intel и на AMD.
Все оказалость намного интереснее. CreateFileMapping работает очень быстро (7-8 тысяч тактов на моем Athlon64) даже при выделении блоков памяти порядка 64Мб — и при этом создаваемые страницы считаются зануленными. Т.е. сама подготовка памяти происходит моментально. Правда, можно проиграть при ее реальном использовании
Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Здравствуйте, MShura, Вы писали:
M> Здравствуйте, zix, Вы писали: M> zix>> Здравствуйте, Alfarn, Вы писали: M> A>>> Есть массив DWORD'ов. Нужна максимально быстрая процедура для его A>>> обнуления. memset не предлагать! M> zix>> Я думаю, быстрее чем команда STOS ничего найти не удастся... M> M> Крис Касперски описывал алгоритм, который позволяет сделать функции M> memset,memcpy быстрее. Для этого надо знать про кэши. Суть в том, чтобы M> периодически обращаться к памяти, которая будет обрабатываться позже, а M> затем продолжать операции. В итоге к тому моменту когда очередь дойдет M> до той памяти, её содержимое уже будет в кэше.
Непонятно. Если ты последовательно забиваешь память нулями, то каждая линейка кэша будет ровно один раз прочитана и ровно один раз сброшена обратно — чего хитрить-то?
-- Всего хорошего!
-- Alex Alexandrov, e-mail: alex_alexandrov@fromru.com
Posted via RSDN NNTP Server 1.9 beta
It's kind of fun to do the impossible (Walt Disney)
[Sorry, skipped]
M>> Крис Касперски описывал алгоритм, который позволяет сделать функции M>> memset,memcpy быстрее. Для этого надо знать про кэши. Суть в том, чтобы M>> периодически обращаться к памяти, которая будет обрабатываться позже, а M>> затем продолжать операции. В итоге к тому моменту когда очередь дойдет M>> до той памяти, её содержимое уже будет в кэше. AA> AA> Непонятно. Если ты последовательно забиваешь память нулями, то каждая AA> линейка кэша будет ровно один раз прочитана и ровно один раз сброшена AA> обратно — чего хитрить-то? AA> AA> -- Всего хорошего! AA> -- Alex Alexandrov, e-mail: alex_alexandrov@fromru.com
Хотя стоп — если имелась в виду инструкция PREFETCH, то нормально — действительно поможет.
-- Всего хорошего!
-- Alex Alexandrov, e-mail: alex_alexandrov@fromru.com
Posted via RSDN NNTP Server 1.9 beta
It's kind of fun to do the impossible (Walt Disney)
Здравствуйте, Alex Alexandrov, Вы писали:
AA>Хотя стоп — если имелась в виду инструкция PREFETCH, то нормально — действительно поможет.
А я вот не думаю, что поможет. Что проку от предвыборки, если нам надо не читать, а писать данные? Тут наоборот должна помочь именно опережающая запись одной ячейки в следующую страницу. Потому что 1) в отличие от чтения, код не ждет завершения операции (из-за чего, видимо, и ввели PREFETCH), 2) выполняется TLB priming. А больше вроде как ничего и не надо.
А чем вас, господа, не устроило мое предложение поизвращаться с файл-маппингом?
Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Здравствуйте, Slicer [Mirkwood], Вы писали:
SM> Здравствуйте, Alex Alexandrov, Вы писали: SM> AA>> Хотя стоп — если имелась в виду инструкция PREFETCH, то нормально - AA>> действительно поможет. SM> SM> А я вот не думаю, что поможет. Что проку от предвыборки, если нам надо SM> не читать, а писать данные? Тут наоборот должна помочь именно SM> опережающая запись одной ячейки в следующую страницу. Потому что SM> 1) в отличие от чтения, код не ждет завершения операции (из-за чего, SM> видимо, и ввели PREFETCH), 2) выполняется TLB priming. А больше вроде SM> как ничего и не надо. SM> SM> А чем вас, господа, не устроило мое предложение поизвращаться с SM> файл-маппингом? SM> SM> Slicer
Политика записи по умолчанию — write-back. Т.е. происходит запись в кэш и немедленный возврат, а в основную память данные записываются асинхронно. Однако кэш и для чтения, и для записи один, поэтому перед записью линейка должна быть прочитана из памяти. PREFETCH помогает это сделать заранее.
Насчет TLB. Кэширование и трансляция адресов — вещи ортогональные. Одно другому не мешает. Только опять же упредить трансляцию адресов можно только в случае наличия аналога PREFETCH для TLB. Т.е. что-то типа PREFETCHTLB. Поскольку в противном случае (попытки упреждающей записи одной ячейки в следующую страницу) при последовательном проходе (мы же рассматриваем обнуление, так?) выгоды не будет. Какая разница, произойдет TLB_MISSED penalty на твоей упреждающей записи или на первой записи при реальном переходе на следующую страницу...
-- Всего хорошего!
-- Alex Alexandrov, e-mail: alex_alexandrov@fromru.com
Posted via RSDN NNTP Server 1.9 beta
It's kind of fun to do the impossible (Walt Disney)
AA>Политика записи по умолчанию — write-back. Т.е. происходит запись в кэш и немедленный возврат, а в основную память данные записываются асинхронно. Однако кэш и для чтения, и для записи один, поэтому перед записью линейка должна быть прочитана из памяти. PREFETCH помогает это сделать заранее.
Хм, надо обратиться к первоисточникам. На самом деле, идея та, что микрооперации инструкции записи начнут выполнение в одном из конвейеров, а т.к. от результата записи никакие другие инструкции кода не зависят, то можно сразу же запускать последующие команды кода. Но это что касается "deeply pipelined" процессоров (как P4), а вот для остальных, может, и не так.
AA>Насчет TLB. Кэширование и трансляция адресов — вещи ортогональные. Одно другому не мешает. Только опять же упредить трансляцию адресов можно только в случае наличия аналога PREFETCH для TLB. Т.е. что-то типа PREFETCHTLB. Поскольку в противном случае (попытки упреждающей записи одной ячейки в следующую страницу) при последовательном проходе (мы же рассматриваем обнуление, так?) выгоды не будет. Какая разница, произойдет TLB_MISSED penalty на твоей упреждающей записи или на первой записи при реальном переходе на следующую страницу...
Ну, см. предыдущий ответ — penalty на данную операцию записи ведь не вызовет полный останов всех конвейеров.
Slicer
Специалист — это варвар, невежество которого не всесторонне :)
SM>Ну, см. предыдущий ответ — penalty на данную операцию записи ведь не вызовет полный останов всех конвейеров.
А, не то объяснял )) В смысле — так застрянет одна операция, а если выполнить первый доступ к странице уже при последовательной записи, застрянут столько инструкций, сколько успеет набиться в конвейеры до окончания загрузки в TLB. Хотя, с другой стороны, может, ты и прав — все равно, вроде бы (не уверен) микрооперации доступа к памяти обслуживает всего один модуль процессора, так что ждать в любом случае придется всем последующим пишущим инструкциям.
Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Здравствуйте, Alfarn, Вы писали:
A> Есть массив DWORD'ов. Нужна максимально быстрая процедура для его A> обнуления. memset не предлагать!
Еще, кстати, можно ускорить обнуление путем его отложения на некоторое время. Суть простая: памяти делается VirtualProtect(PAGE_NOACCESS), потом ловим исключения Access Violation и если ошибка доступа произошла на странице из нашего массива — делаем VirtualProtect(PAGE_READWRITE), обнуляем страницу и возвращаем EXCEPTION_CONTINUE_EXECUTION. Особенно может улучшить производительность, если не факт, что вся обнуляемая память будет задействована в будущем. Минусы — код обнуления размазывается по нескольким местам.
-- Всего хорошего!
-- Alex Alexandrov, e-mail: alex_alexandrov@fromru.com
Posted via RSDN NNTP Server 1.9 beta
It's kind of fun to do the impossible (Walt Disney)
Здравствуйте, mokc0der, Вы писали:
M>Здравствуйте, OpenGL, Вы писали:
OGL>>mmx,(3dnow, 3dnow+) sse, sse2 самое быстрое последнее 128 бит за цикл. M>не возможности протестировать sse2, но разницы между обнулением через mmx(64bit) и xmm(128bit) нет ни какой. Это протестировано
Дело в том что я имел ввиду параллельное выполнение и использование U V конвееров тогда разница будет заметна.
Там за такт два mov можно сделать!
Re: Как быстро обнулить массив?
От:
Аноним
Дата:
29.08.04 10:02
Оценка:
Здравствуйте, Alfarn, Вы писали:
A>Есть массив DWORD'ов. Нужна максимально быстрая процедура для его обнуления. memset не предлагать!
Можно попробовать посмотреть на результаты компилятора Intel C Compiler. Я предполагаю использование /O3. При том, что компилятор может вычислить размер обнуляемой памяти на этапе компиляции он генерирует примерно следующий код. Первой инструкцией он обнуляет 1 из xmm регистров (SSE инструкции), а затем просто выполняет mov за movом в память, двигаясь от младших адресов к старшим. Таких movов лично я видел пару страниц. Учитывая, что обнуление памяти — частая задача, я думаю, что такое поведение специально запрограммировано и даёт наилучшие результаты. Естествено для P4. Будет ли такой код оптимальным для Athlon неизвестно.