x86 Optimization Manuals

Тема в разделе "WASM.A&O", создана пользователем cppasm, 29 дек 2008.

  1. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    cppasm
    Других нет ;) Во-первых, в P4 только два порта запуска fpu-команд - один для fld\fst и т.п., другой для всего прочего, к тому же эти порты являются общими с целочисленными операциями, а в атлонах все fpu\mmx\sse выделены в отдельный конвеер с тремя портами запуска для умножений\делений, сложений\вычитаний и пересылок. Поэтому, например, в P4 нельзя одновремено запустить fmul и fadd, а в АМД можно. Во-вторых, латентности основных операций в P4 завышены до 1.5-2 раз по сравнению с АМД
     
  2. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Ясно, вроди со всем разобрался.
    Спасибо за помощ.

    PS: leo ты был прав - на стаканчик не хватило. :) свернул всё назад.
     
  3. AndreyMust19

    AndreyMust19 New Member

    Публикаций:
    0
    Регистрация:
    20 окт 2008
    Сообщения:
    714
    Слушайте, а как на ring3 получить значение esp регистра кроме команды call?
     
  4. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Так же как и в ring0 :)
    mov eax,esp

    Или ты хотел eip?
    Тогда только через call, по другому никак (ring0 или ring3 значения не имеет).
     
  5. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
    Так пойдёт?
    Код (Text):
    1. eip_value:mov eax,eip_value
    Вот поинтереснее вопрос. В мануале AMD есть пример.
    Код (Text):
    1. #define CACHEBLOCK 400h   // QWORDs in a block, (8K bytes)
    2. int* storedest
    3. char buffer[CACHEBLOCK * 8]  // in-cache temporary storage
    4.     mov  esi, [src1]      // source array one
    5.     mov  ebx, [src2]      // source array two
    6.     mov  edi, [dst]       // destination array
    7.     mov  ecx, [len]  // number of Floats (8 bytes)
    8.                       // (assumes len /CACHEBLOCK = integer)
    9.     lea  esi, [esi+ecx*8]
    10.     lea  ebx, [ebx+ecx*8]
    11.     lea  edi, [edi+ecx*8]
    12.     mov  [storedest], edi // save the real dest for later
    13.     mov  edi, [buffer]    // temporary in-cache buffer...
    14.     lea  edi, [edi+ecx*8] // stays in cache from heavy use
    15.     neg  ecx
    16. mainloop:
    17.     mov  eax, CACHEBLOCK / 16
    18. prefetchloop1:                 // block prefetch array #1
    19.     mov  edx, [esi+ecx*8]
    20.     mov  edx, [esi+ecx*8+64]   // (this loop is unrolled 2X)
    21.     add  ecx, 16
    22.     dec  eax
    23.     jnz  prefetchloop1
    24.     sub  ecx, CACHEBLOCK
    25.     mov  eax, CACHEBLOCK / 16
    26. prefetchloop2:                 // block prefetch array #2
    27.     mov  edx, [ebx+ecx*8]
    28.     mov  edx, [ebx+ecx*8+64]   // (this loop is unrolled 2X)
    29.     add  ecx, 16
    30.     dec  eax
    31.     jnz  prefetchloop2
    32.     sub  ecx, CACHEBLOCK
    33.     mov  eax, CACHEBLOCK / 8
    34. processloop:           // this loop read/writes all in cache!
    35.     fld  qword ptr [esi+ecx*8+56]
    36.     fadd qword ptr [ebx+ecx*8+56]
    37.     ...
    38.     fld  qword ptr [esi+ecx*8+0]
    39.     fadd qword ptr [ebx+ecx*8+0]
    40.     fstp qword ptr [edi+ecx*8+0]
    41.     ...
    42.     fstp qword ptr [edi+ecx*8+56]
    43.     add  ecx, 8
    44.     dec  eax
    45.     jnz  processloop
    46.     emms
    47.     sub  ecx, CACHEBLOCK
    48.     mov  edx, [storedest]
    49.     mov  eax, CACHEBLOCK / 8
    50. writeloop:                     // write buffer to main mem
    51.     movq  mm0, qword ptr [edi+ecx*8]
    52.     ...
    53.     movq  mm7, qword ptr [edi+ecx*8+56]
    54.     movntq  qword ptr [edx+ecx*8], mm0
    55.     ...
    56.     movntq  qword ptr [edx+ecx*8+56], mm7
    57.     add  ecx, 8
    58.     dec  eax
    59.     jnz  writeloop
    60.     or   ecx, ecx
    61.     jge  exit
    62.     sub  edi, CACHEBLOCK * 8 // reset edi back to start of
    63.                              // buffer
    64.     sfence
    65.     emms
    66.     jmp  mainloop
    67. exit:
    Откуда такая уверенность, что при fstp qword ptr [edi+ecx*8+0] будет произведена запись во временный буфер в кеше, а не в память? По-моему в цикле writeloop: производится лишняя работа т.к. данные окажутся в памяти после fstp.
     
  6. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    murder
    Не совсем понял, что имеется ввиду. Есть буфер записи. А есть кеш. Две разные, хотя и связаные сущности.

    Практически во всех современных процессорах используется Write-Back кеш. Запись всегда производится в него. Если запись производится в строку, которой в кеше нет, онасначала в него загружается, если не используется команда типа MOVNTQ. А она как раз здесь используется. Смысл этого примера в том, чтобы обрабатывать данные во временной области памяти, которая находится в кеше, а потом сбрасывать всю порцию в память, минуя кеш.
     
  7. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
    Ustus
    MOVNTQ копирует данные в буфер записи, затем SFENCE копирует буфер записи в память. Это понятно.
    movq mm0, qword ptr [edi+ecx*8] читает кешированые данные и тут всё в порядке.

    Почему процессор не начнёт копировать данные из кеша в память параллельно с циклом processloop не дожидаясь writeloop?
     
  8. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    murder
    Процессор сбрасывает обычные WB-данные из кэша в ОЗУ, только когда "рак на горе свистнет" - т.е. либо возникнет конфликт адресов или данных в кэше, либо принудительно командами CLFLUSH, INVD\WBINVD.
    Конфликт адресов для данной строки кэша возникает при поступлении запроса на чтение\запись по адресу, проецируемому на тот же набор кэша, в котором находится данная строка и эта строка является наиболее "старой" в наборе. В современных процах ассоциативность кэша L2 составляет 8 или 16-way, поэтому чтобы некоторая строка была вытеснена другой нужно, чтобы после последнего обращения к этой строке произошло как минимум 8 или 16 обращений по разным физическим адресам, проецируемым на тот же набор.
    Конфликты данных могут возникать при использовании некэшируемой записи movnt по адресам, совпадающим с адресом модифицированной линейки кэша (в этом сл.сначала происхдит сброс линейки в ОЗУ), либо в многопроцессорных системах с раздельными кэшами данных при одновременном обращении разных потоков к модифицированным линейкам кэша с одинаковыми физ.адресам
    Поскольку в приведенном коде - ничего подобного нет, то данные на достаточно коротком интервале времени могут быть вытеснены из кэша только "чудом" - другим потоком, интенсивно работающим с памятью
     
  9. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
    leo
    Э-э-э тогда какая польза от writeloop? Ведь мы тратим лишнее время на копирование данных из кеша в память, в то время как эта операция может быть выполнена параллельно с другим кодом когда "рак на горе свистнет".
     
  10. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    murder

    Отличие некэшируемой записи movnt от обычной WB-записи заключается в том, что при movnt данные копятся в спец.WC-буферах (write combining) и затем целыми линейками по 64 байта пишутся прямо в ОЗУ, минуя кэш. А при обычной кэшируемой записи для обеспечения когерентности памяти данные сначала считываются из ОЗУ (RFO - read for ownership), затем изменяются в кэше и пишутся обратно в ОЗУ когда "рак на горе свистнет", т.е. когда случится конфликт адресов. Поэтому при кэшируемой записи больших объемов данных, во-первых, имеем лишний бесполезный "поток" чтения RFO, как минимум вдвое снижающий проп.способность шины памяти, и, во-вторых, сброс данных в ОЗУ идет "бесконтрольно" во время их вытестнения новыми RFO, что добавляет лишних тормозов из-за чередования чтения и записи в ОЗУ по разным далеко отстающим адресам.

    Поэтому суть приведенного примера заключается в том, что выделяется сравнительно небольшой блок памяти, умещающийся в быстром кэше L1, один раз производится его чтение из ОЗУ (мусора из стека) и затем этот блок постоянно находится в кэше, т.к. к нему идут частые обращения в цикле и соотв-но доступ к даным на чтение\запись произв-ся очень быстро. После заполнения блока он сбрасывается в ОЗУ через movntq - при этом рулит один "поток" записи по последовательным адресам и соотв-но шина памяти работает на макс.проп.способности, т.к. никаие RFO "не путаются под ногами" ;)
     
  11. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
    leo
    Ну вроде понял :)

    А вот допустим вышло так, что в виртуальном пространстве адресов я работаю с единым блоком, а в физическом он сильно фрагментирован. Это сильно скажется на производительности?
     
  12. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    murder
    Во-первых, физ.адреса выделяются страницами по 4К, поэтому "непоследовательность" проявляется только при переходах от одной 4К страницы к другой.
    Во-вторых, говорить о влиянии "фрагментации" физ.адресов на производительность не имеет практического смысла, т.к. сравнивать не с чем ;) Физ.память это вотчина ОС и думать о ее фрагментации должны разработчики ОС и железа, а нам остается только принцип "ешь, что дают" ;)
     
  13. persicum

    persicum New Member

    Публикаций:
    0
    Регистрация:
    2 фев 2007
    Сообщения:
    947
    Люди добрые, подскажите как правильно разруливать циклы?
    Некоторые свои циклы я разруливаю на 4 операции, некоторые на 8, а некоторые аж на 32...
    Чем нужно руководствоваться при выборе степени разруливания?
     
  14. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
  15. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Появился тут у меня вопрос по зависимостям.
    Т.е. в каком случае команды считаются зависимыми?
    К примеру
    Код (Text):
    1. mov reg1,[mem1]
    2. add reg1,reg2
    тут понятно что есть зависимость по регистру reg1.

    А вот здесь
    Код (Text):
    1. mov [mem1],reg1
    2. mov reg1,[mem2]
    или
    Код (Text):
    1. mov [mem1],reg1
    2. add reg1,reg2
    не очень понятно.
    По-моему во втором варианте зависимости нет.
    Или я ошибаюсь?
     
  16. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    В варианте "или" - вообще никаких зависимостей нет, т.к. кажущаяся зависимость по reg1 в современных процах устраняется за счет переименования регистров
    В варианте "А вот здесь" - зависимости по reg1 нет, но на "осторожных" атлонах может быть квазизависимость по порядку вычисления адресов mem1 и mem2, т.е. операция mov reg1,[mem2] не может быть выполнена раньше mov [mem1],reg1 пока проц не вычислит адрес mem1 и не убедится в том, что mem2 != mem1 (AGI - address generation interlock)
     
  17. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
    leo
    А я почему-то подумал, что AGI - это просто долгое вычисление составного адреса (в примере ниже eax+ebx).
    Код (Text):
    1. MOV ECX, DWORD PTR [EAX+EBX] ;inst 3 (slow address calc.)
    2. MOV EDX, DWORD PTR [24h]     ;this load is stalled from
    3.            ; accessing data cache due
    4.            ; to long latency for
    5.   ; generating address for
    6.   ; inst 3
     
  18. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    leo спасибо ;)
    Я собственно так и думал, но в мануалах нигде точного описания этого момента не нашёл.
    Да, насчёт этого я в AMD Athlon x86 Code Optimization Manual читал.
    Интересовала именно зависимость по регистрам.

    По сути получается что зависимости есть при записи в регист и последующих операциях с ним.
    А при чтении из регистра и последующих операциях зависимостей нет.

    Ну так leo про это и написал.
    Просто в моих "записях" под mem1 и mem2 могут скрываться разные выражения.
     
  19. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    murder
    Не обязательно долгое и не обязательно составного. Например:
    Код (Text):
    1. inc ebx  ;(1)
    2. mov ecx,[ebx]  ;(2) - явная зависимость от (1) по ebx
    3. mov edx,[24h]  ;(3) - независимая команда
    Загрузка edx не зависит по данным от команд (1) и (2) и соотв-но на пеньках м.б. выполнена одновременно или даже раньше inc ebx. А на атлонах все адреса вычисляются строго последовательно, поэтому в лучшем случае mov edx может поступить на исполнение одновременно с mov ecx, но не раньше. Т.е. AGI по сути приводит к тому, что зависимость от изменения регистра распространяется не только на команды, в которые этот регистр явно входит в операнд памяти, но и на все последующие команды, работающие с памятью
     
  20. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Что-то я зачастил :)
    Вопрос к тем кто разбирался с архитектурой AMD.
    В соответствии с ихними мануалами процессор может одновременно выполнять до 4 команд fpu.
    У меня вопрос - почему 4, исполнительных устройства ведь 3: FADD, FMUL, FSTORE.
    На практике мне это надо для упорядочивания инструкций (scheduling).
    Т.е. к примеру если у FMUL латентность 4 такта, результаты раньше этого времени желательно не использовать.
    Вопрос в том как эти такты перевести в число инструкций, т.е. через сколько команд можно ипользовать результаты.
    Плюс не очень понятно - если латентность 4 такта, FMUL будет занят все 4 такта, или один?
    По-моему один, но я могу и ошибаться :)