Эффективность предварительных операций загрузки

Тема в разделе "WASM.A&O", создана пользователем v_mirgorodsky, 22 фев 2007.

  1. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Пусть есть некий регистр с которым необходимо выполнить операцию, вторым операндом которой выступает значение, находящееся где-то в иерархии кеши/память. Что эффективнее - непосредственно загрузить значение в некий регистр и лишь потом выполнить операцию регистр-регистр или все же использовать операцию с операндом из памяти?

    Немного предыстории. У меня был код, который специфично перетасовывал байты в пределах 16 байтного блока. Последовательность была следующей: я грузил данные в два MMX регистра, выполнял над ними серию shift'ов перемешанных с pack/unpck, после чего результат складывал в память. Естественно, таких блоков в серии было много. Вариант, использующий для первоначальной загрузки несколько pack/unpck с операндами из памяти заработал быстрее. Внятного объяснения этому поведению в Intel Optimization Manual я найти не смог. Более того, во всех своих примерах Intel старается не пользоваться операндами из памяти и предварительно загружать все значения в регистры :-\
     
  2. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    посмотри, какой код выдает компилятор с включенной полной оптимизацией по скорости
     
  3. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Компилятор сложно заставить использовать SIMD инструкции без специфических изысков, но из того, что удалось добыть - грузит в регистры и делает операции над регистрами. Более того, если идет поток операций по работе с АЛУ, то компилятор очень редко размещает переменные в регистрах. Как-то даже попался перл, в котором счетчик цикла доставался и укладывался в память на каждой итерации, хотя в самом теле цикла использовались всего четыре регистра.
     
  4. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Очевидная "эффективность" использования команд с операндами памяти заключается в экономии регистра и размера кода, что собс-но и отмечается во всех мануалах ;). А вот в отношении скорости не все так однозначно. С одной стороны в продвинутых PM, Core и атлонах такие команды рассматриваются как один макрооп, который требует только одного слота стадий декодирования и отставки, что способствует увеличению общей пропускной способности. Для P4 с его Т-кэшем в этом смысле д.б. все равно, т.к. операции с памятью по любому раскладываются на два мопа, а для первых P6 и вовсе две одномоповые операции могут быть предпочтительнее одной двухмоповой из-за ограничений декодера 4-1-1. С другой стороны операции с памятью по любому разделяются в планировщике на два мопа, но в отличие от раздельных операций эти мопы идут один за другим, что в общем случае ограничивает возможности переупорядочивания. Поэтому в общем случае сказать однозначно, что будет быстрее, нельзя и нужно смотреть конкретный код
     
  5. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    leo
    Круто завернул !!!
    Всё таки недопонимаю - при работе с операндом в памяти (в многократно исполняемом цикле) получается что все обращения к памяти идут через L1 кэш и реально записываются в память только в конце цикла?
    И тогда отличие в скорости между обращением к регитру и памяти отличается только описанными тобой особенностями поведения мопов? (т.е. отличие копеешное и оно не всегда есть)
    А других тормозящих факторов при использовании памяти вместо регистра (в цикле) получается нет?
     
  6. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Y_Mur
    Мда, ты "Круто завернул !!!" ;)
    1) Вообще-то тут речь шла не о "использовании памяти вместо регистра", а о том, что быстрее - использовать одну инструкцию с операндом в памяти, например add eax,m32 или две раздельных mov edx,m32 + add eax,edx
    2) Чтение операнда "в памяти" производится поэтапно - сначала проверяется нет ли запрашивемого адреса в буферах записи (store-to-load forwarding), если нет, то выдается запрос в L1, затем в случае промаха - в L2 и уж затем в ОЗУ. Запись в память производится примерно также - сначала данные вместе с адресом помещаются в буфер записи и сидят там n-е число тактов до выхода операции записи в отставку. Затем проверяется наличие соотв. линейки в L1 - если есть, то производится запись. Если нет, то делается запрос в L2 - если есть, то в P4 запись производится в L2, а в других процах линейка копируется в L1 и даные пишутся в L1. Ну а если линейки нет в L2, то делается запрос на ее чтение из ОЗУ (read-for-ownership) и записываемые данные временно помещаются в один из WC-буферов до поступления линейки из ОЗУ. Данные могут находится в кэшах L1, L2 неопределенное время, пока не будут вытеснены другими данными или не будут сброшены в ОЗУ принудительно (clflush,wbinvd).
    Вывод: проц всегда старается работать с тем, что "ближе лежит", и не торопится сбрасывать данные в ОЗУ
    3) Из этого следует, что отличие между обращением к регистру и к памяти не "копеешное", а очень даже существенное. Во-первых, операции чтения и записи сами по себе требуют доп.мопов, во-вторых, их латентность составляет не менее 3-х тактов (при наличии данных в буфере или в L1), в-третьих, существуют ограничения на число буферов записи и на внеочередное исполнение команд чтения и записи. Операции записи на всех процах производятся строго в программном порядке, а с чтениями дела обстоят хитрее\сложнее. AMD придерживаются осторожно-консервативного подхода - в атлонах вычисление адреса чтения или записи (точнее формирование очереди MOB LSU) выполняется в программном порядке, поэтому с одной строны невозможны ошибки преждевременного чтения данных, которые еще не были записаны, но с другой стороны возникает проблема AGI - если вычисление адреса одной из операций задерживается, то все последующие чтения\записи вынуждены ее ждать (поэтому всякие табличные преобразования в циклах на атлонах работают хуже, чем на пеньках). Другая крайность - в P4 model<=2 чтения производятся вообще независимо от записей, поэтому возможны ситуации преждевременного чтения с последующим их переисполнением (реплеем), что может приводить к большим пенальти. Но это не означает, что такой подход в принципе плох, т.к. если между записью и чтением есть несколько инструкций, "притормаживающих" чтение, то все получается "путем" и даже быстрее, чем в более осторожных P4Е с неким анализом "возможной зависимости чтения от предшествующей записи" (вроде в каком-то топике v_mirgorodsky я приводил сравнительный примерчик).
    Вывод: с чтением\записью в память все не так просто (особенно в циклах), поэтому если есть возможность работать с регистрами, то лучше избегать обращений к памяти или же делать это с умом, чтобы не нарваться на реплей или AGI и на ограничение числа буферов записи
     
  7. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    leo
    Спасибо :)
    Значит буду по прежнему: "если есть возможность работать с регистрами, то лучше избегать обращений к памяти", а то я было обрадовался, что кэш можно как суперрегистр рассматривать :))
     
  8. asmfan

    asmfan New Member

    Публикаций:
    0
    Регистрация:
    10 июл 2006
    Сообщения:
    1.004
    Адрес:
    Abaddon
    Возможно, всё было давно отвечено, но вот вопрос: программная реализация предвыборки довольно прозрачна (prefetch*), т.е. понятно что и куда загружается (уровни кэша), а вот аппаратная... Допустим код
    Код (Text):
    1.   mov  eax,[someMemVar]
    или последовательность обращений к одному и тому же адресу (имеется ввиду чтение), аппаратная предвыборка т.н. как будет работать? какая из prefetch* будет аналогом?
     
  9. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    asmfan
    Я бы сказал наоборот, т.к. хардварный префетч на всех процах работает примерно одинаково, а вот все prefetch* являются implementation specific несмотря на свои формальные названия. Например, в P4 все prefetch* грузят данные в L2, а в атлонах все в L1, причем ни Intel, ни AMD не гарантируют что этот порядок не изменится в будущем. Единственное гарантированное отличие имеет только prefetchnta - данные грузятся только в один фиксированный way и поэтому "засоряют" не более 1/ways от всего объема кэша.
    А хардварный префетч на всех процах грузит данные в L2, срабатывает после промахов двух соседних линеек (в P4 несколько навороченнее, но суть та же) и затем пытается автоматически поддерживать опережающую загрузку данных (от одной до 4-х линеек вперед в завис-ти от проца).
    Подробности см. в мануалах по оптимизации IA-32 и AMD 64
     
  10. asmfan

    asmfan New Member

    Публикаций:
    0
    Регистрация:
    10 июл 2006
    Сообщения:
    1.004
    Адрес:
    Abaddon
    Пасиба