Одной строкой: mov[a|nt]ps

Не могу молчать:

Есть такие вот "ассемблерные" макросы:

  • _mm_store_ps(указатель, XMM-регистр) - писать в память.
  • _mm_stream_ps(те же параметры) - писать в память мимо кэша.

Для первого из них генерируется инструкция movaps (Intel C++ в некоторых условиях генерирует movntps, чему я удивлялся всегда). Для второго - всегда movntps ("писать мимо кэша"). В теории, при обработке больших потоков данных вторая быстрее.

Я неопытный сварщик SSE-ассемблером занялся не так давно, на рабочей станции был уже Core I7 и на Core2 я свои изделия (которые пока для internal use) - не запускал почти. А тут - запустил. Удивился тормозам. Помикробенчмаркал. На коротком цикле, вроде поминаемых тут скалярных умножений, получил разницу в четыре раза.

В том смысле, что movntps - в 4 раза медленнее. 40 мегапикселей вместо 160. Устойчиво, от компилятора не зависит, и на gcc так и на Clang.

В-принципе, от mm_stream выигрыша большого на i7 не было. Похоже, лучше про нее вообще забыть.

Comments

Сам интел пишет, что записи, ининциируемы movntps исользуют WC-протокол, т.е кешируются только во write-буферах процессора. Поскольку буферов единицы (максимум десятки) штук, записи, после заполнения имеющихся буферов, начинают выполнятся на скорости памяти. Буферы имеют размер строки кеша, так то срабатывает C (combining) и запись идет все-таки быстрее чем совсем медленная память, но медленнее, чем в кеш.

У меня данных - 1.6Gb, поэтому кэш уже несущественен.

На i7 movntps (для больших объемов) был заметно быстрее, на i7-AVX я не вижу большой разницы, на Core2 - медленнее и сильно.

Чудеса.

Могу поугадывать.

Если тестовым примером был код из http://blog.lexa.ru/2011/09/01/o_kompilyatorakh_i_protsessorakh.html, то хочу обратить внимание, что тут производится кешируемое чтение по тем же страницам, по которым идет WC-запись. Интел называет это неопределенным поведением и требует обязательно использовать как минимум store fence вокруг WC-записей.

Мое предположение состоит в том, что self-snoop на core i7 и старше замечает, что строка уже в кеше, а на старых Core2 нет.

Даже на терабайтных записях кеш помогает за счет более гибкого write coalescing. Писатели драйверов видеокарт давно знают, что линейная запись текстуры в основной памяти и ремап в апертуру в разы быстрее записи через WC-апертуру.

Ну так чтение происходит *до* записи, после записи - по тому же адресу ничего не читается.

История обсуждается вот тут в комментариях: http://blog.lexa.ru/2010/12/21/polna_chudes_moguchaya_priroda.html
Это примерно тот же самый код по смыслу: прочитали 4 float, что-то посчитали, записали в то же место.

И на i7-920 у меня получалось, что movntps для записи - заметно быстрее, чем movaps. Для i7-avx принципиальной разницы я не вижу.

Я именно об этом и говорю - строка уже в кеше, и процессор может, но не обязан, это заметить. В любом случае, код некорректен из-за отсутствия fence, и все это мои спекуляции.

Про fence я не понимаю.
1) Порядок, гарантированно, чтение - обработка - запись. Поэтому никакая сериализация (за исключением конца цикла) не нужна.
2) Столь же гарантированно, если я вдруг чего пишу через movntps по адресу Х, то по адресу Х+16 ничего *самопроизвольно* писаться не будет (иначе бы вся эта write-combining memory все бы разваливала нахрен если такая запись как-то происходит без учета состояния кэшей).

Так зачем fence?

Если бы обработка была "чтение по адресу Х, обработка, запись по адресу Х+1" - тогда я понимаю. Но обработанный (прочитанный-обработанный-записанный) элемент данных - не трогается.

BTW, в каких-то ситуациях intel сам лепит movntps без (m|l|s)fence

Я пробовал как то _mm_stream_ps на Core2Duo. Медленнее, чем обычный store. Тоже не понял, в чем же фокус.

Моя гипотеза - вымывается кэш.

Поэтому чисто write (залить память константой) должно быть быстро, а read-write - медленно.

Сейчас попробую.

MOVNTPS нужно использовать только для тех участков памяти, которые не читаются в кэш. Если данные в кэше, прироста в скорости он не даст

Мои измерения показывают другое: http://alextutubalin.livejournal.com/253179.html

i7 по-моему сильно оптимизирован под потоки данных, то есть нельзя возвращаться к старым данным, нельзя писать туда, откуда читали, даже из IPP функции in-place интел обещает выкинуть - они не хотят поощрять дизайн, в котором данные пишутся обратно

Дизайн приложения должен быть устроен так, что данные читаются последовательно read-only, пишутся последовательно write-only в другое место, и кэш, как таковой, только сглаживает неровности, вроде отсутствия выравнивания (которое по сравнению с Core2/P4 более не критично)

Возможно.

Вместе с тем, все микробенчмарки, описанные в данной серии постов - это именно про запись in place.

Потому что ситуация же типичная:
- взяли откуда-то данные (приехали из космоса), записали в буфер
- посчитали по ним, к примеру, статистику (максимум, скажем)
- нормализовали эти данные по вышеуказанной статистике (ну вот поделили на этот максимум, чтобы получить все в диапазоне 0..1)

Но вообще, интересная тема, я поизучаю не будет ли *сильно* быстрее иметь два буфера. Память сейчас дешевая, битов в адресе 64 и все такое.

интересно, что АМД ведет себя в соответствии с интуитивными представлениями о том, что такое кеш и что такое память, а Интел - совершенно по иному, контринтуитивно, он как бы *потоковый* процессор

Потоковая парадигма - очень хорошая во всех смыслах (оптимизировать проще, масштабировать на много ядер - проще).
Но насколько она forced - надо проверять.

Про IPP вот я почитал, из интеловской цитаты неочевидно, что они хотят именно потоковости:
"In-place functionality will be removed: In-place functions accept only one pointer for input and output. The out-of-place versions often offer the same functionality but with the additional flexibility of specifying a different output buffer."

Т.е. намекают, что можно in и out указать одинаковые и будет хорошо. Вопрос про buffer (pointer) aliasing остается, возможно что исполнение пойдет разными путями при перекрывающихся буферах.

Но, повторяю, нужно тестировать.

если включить воображение

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

вообще, память больше напоминает жесткий диск с той разницей, то потоков может быть одновременно несколько (но и здесь, наверное, есть ограничения)

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

Процессор "не настолько быстрее памяти". Был бы "настолько быстрее" - любой stream-like код разумной сложности работал бы со скоростью памяти. А этого и близко нет.

в каком смысле? скорость памяти - это memcpy? код memcpy работает со скоростью memcpy, нo и любой аналогичный по характеру обращения к памяти код работает со скоростью memcpy

Да, поднял старые таблички: http://blog.lexa.ru/2011/09/08/opyat_pro_movntps.html

Просто copy: 11.7 GB/sec (чтобы получить memory b/w - умножаем на два)
Довольно немаленькая работа: 9.4 GB/sec.

Разница есть, но в пределах разумного, 20%. Там по старым процессорам разброс большой, надо было инструментировать и смотреть, но тех уж нет...

самый прикол, что обычный memcpy или даже просто rep movsd не отличается от movaps по скорости

В "обычном memcpy" (в glibc, например), если присмотреться, внутри будет movaps. А то и еще чего похлеще, вспоминаем скандал с адобом и вот ровно memcpy

возможно,что на каком-нибудь Атоме или Пентиуме-Ш это критично, вот для них они и стараются

но в rep movsd точно нет внутри movaps, хотя кто знает ;)

Наоборот, стараются для core2 и новых i7 в x64-режиме. Этож знаменитая история: http://blog.lexa.ru/2011/03/31/subbota_dlya_cheloveka_ili_chelovek_dlya_...

Т.е. там конечно не movaps/movntps, а movntiq (потому что не через xmm, а через gp registers), но никаких rep movsd!

rep movsd отвратительно работает на некотором подмножестве процессоров, но эти времена уже позади

начиная как раз с core2 и продолжая на core i7 все стало по-другому, все сильно зависит от того, что именно делается, но слабо - от того, как именно

видимо все транслируется в какой-то внутренний уровень абстракции, где все равно

а история с перекрытием - помню, было такое, на мой вкус нужно было просто выводит варнинг в дебаг output, что области перекрываются, и вызывать memmove, вот и все

в прошлых версиях out-of-place функции по факту корректно не работали при совпадающих указателях, то есть гибкости не было