Скорость+оптимизация

Тема в разделе "WASM.A&O", создана пользователем Sonic, 19 май 2005.

  1. leo

    leo Active Member

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



    Что то вы в трех соснах путаетесь ;)

    Вот стандартный вариант, после которого стек FPU чист и невинен как младенец :))
    Код (Text):
    1.     fld qword [y]
    2.     fld qword [x]
    3.     fyl2x
    4.     fld     st0
    5.     frndint
    6.     fxch    st1  ;поменяли местами
    7.     fsub    st0,st1
    8.     f2xm1
    9.     fld1
    10.     faddp
    11.     fscale
    12.     [b]fstp[/b] qword [z] ;!!! сохраняем и выкидываем
    13.     [b]fstp st0[/b]       ;освобождаем последний регистр


    А ты делаешь fstp st1, которая копирует st0 в st1 и выбрасывает st0, но в итоге у тебя st0 снова оказывается занятым. Но если в приведенном выше коде записать fstp st1 и потом fstp qword [z], то тоже будет правильно (сначала переписываем st0 в st1, а затем в память и выкидываем).

    Если стек освобождать правильно, то FINIT делается один раз вне процедуры.



    Насчет дампов. ИМХО для таких простеньких программок проще загрузить exe-шник в отладчик, например в Olly да посмотреть по шагам - и алгоритм проверишь и очистку стека FPU
     
  2. Sonic

    Sonic New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2004
    Сообщения:
    77
    Адрес:
    Днепропетровск
    Протестируйте на пеньках даст ли замена frndint на fistp +fild какой то положительный результат (так написано у Агнера Фога) лично у меня это ничего не дало, а вот замена

    fstp qword [z] + fstp st0 на emms дает пару тактов. У меня это занимает 201 такт, меньше наверно не получится. Сколько это займёт на Атлоне ?

    P.S. dr_dred «math_x 1500 ticks math_t 1100 ticks» ужасающие цифры !!! Почему так, столько у меня было при разложении в ряд !!!



    [​IMG] _1727279496__Math.zip
     
  3. leo

    leo Active Member

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



    > "dr_dred «math_x 1500 ticks math_t 1100 ticks» ужасающие цифры !!!"



    Чего тут ужасного, у него вроде как Celeron 2.0GHz, т.е. одна из модификаций P4, а у тебя Duron 900 сравнимый с Pentium III. Разные архитектуры, разные конвейры, разные микрокоды трансцендентных операций.



    > "даст ли замена frndint на fistp +fild какой то положительный результат (так написано у Агнера Фога)"



    У Фога также написано, что эта комбинация (fild идет сразу после fistp) опасна с точки зрения возможного пенальти, как и прочие варианты когда загрузка операнда идет сразу после "небыстрой" операции его сохранения. Поэтому Фог советует использовать такую комбинацию, когда можно вставить между fistp и fild дополнительные полезные инструкции. Да и разница на P4 получается небольшая: латентность frndint < 30, а fistp+fild при нормальной загрузке = 10+10=20, но зато в случае ошибки планировщика (если загрузка произойдет раньше выгрузки) можно получить штраф > 20 тиков. А что такое 10 тиков по сравнению с общим временем > 800 на P4 - ИМХО мелочь пузатая :))

    Также как и "замена fstp qword [z] + fstp st0 на emms дает пару тактов" - пара тактов в данном случае это ничто, особенно при твоем методе измерения (ты даже не знаешь какой разброс величин которые ты усредняешь ;)



    А если хочешь заметно сэкономить на FRNDINT и FSCALE, то можно использовать код А.Фога для вычисления экспоненты. Там надо всего лишь первые три инструкции (вычисление x*log2e) заменить на вычисление fyl2x (y*log2x) и усе ;)
     
  4. dr_dred

    dr_dred Сергей

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    301
    Адрес:
    Russia
    Sonic



    С твоей программой так. С моей 404 такта.



    leo

    Вопрос на_засыпку.

    <ol type=1>fsub st(1),st

    fstp st(2) ;Поменяли местами</ol>

    на четыре ракта меньше, чем

    <ol type=1>fstp st(2) ;Поменяли местами

    fsub st,st(1)</ol>



    Я со стеком запутался. Если я делаю fstp st(2) вместо fxch st(1), следует ли в конце процедуры ставить fstp st или ffree st? С ffree быстрее.



    p.s. Если поставить finit перед началом цикла, то это у меня дает 1 такт, нежели если его вообще не ставить.

    [​IMG] 351939040__m404.zip
     
  5. Sonic

    Sonic New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2004
    Сообщения:
    77
    Адрес:
    Днепропетровск
    Вообще я так понял что тут скорость ещё зависит от x & y. Твой аттач где x=2; y=6.5, работает у меня быстрее на 10 тактов (190 т) чем при x=3; y=1.5. У меня замена

    «fsub st(1),st + fxch st(1)» на «fsub st(1),st +fstp st(2)» не дала результатов, но думаю 1-е будет всё же лутше.

    P.S. Интересно, какой характер зависимости скорости от x & y ?
     
  6. Sonic

    Sonic New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2004
    Сообщения:
    77
    Адрес:
    Днепропетровск
    Ах ну да, Log(2,2)=1 и считается как-то быстрее
     
  7. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Sonic

    По-моему тестилка немного врёт. Не пробовал потестить в фоговском wtest?



    От Y зависимости не заметил, а от X время зависит.
     
  8. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    А вообще данный велосипед давно изобретен. Называется FpuXexpY и находится в masm'овской fpu.lib. Если его очистить от гигантского количества проверок, на которые не поскупился тов. Filiatreault, то останется:


    Код (Text):
    1.     mov   eax,[esp+8]
    2.     fld   tbyte ptr [eax]
    3.     mov   eax,[esp+4]
    4.     fld   tbyte ptr [eax]
    5.     fyl2x                  
    6.     fld   st(0)            
    7.     frndint                
    8.     fsub  st(1),st          
    9.     fxch                    
    10.     f2xm1                  
    11.     fld1
    12.     fadd                    
    13.     fscale                  
    14.     fstp  st(1)            
    15.     mov   eax,[esp+12]
    16.     fstp  tbyte ptr[eax]    
    17.     retn 12




    Как говорится, найдите 10 отличий :) От последнего аттача отличается только тем, что fstp st(1) один раз. Поэтому работает чуть быстрее (199 против 203 тиков) для qword'ов, или чуть медленней (205 против 203) для tbyte'ов. В последнем случае точность результата x^y выше.
     
  9. leo

    leo Active Member

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



    > "Вопрос на_засыпку"



    Правильный ответ: если нужно просто обменять содержимое st(0) c st(i), то лучше использовать fxch, которая и в P6 family и в P4 имеет латентность = 0, т.к. она осуществляется на уровне переименования регистров на предыдущих стадиях конвейера. Поэтому между fxch+fsub и fsub+fxch разницы быть не должно.

    А вот fstp st(2) реально копирует содержимое st(0) в st(2), помечает st(0) свободным и перемещает вершину стека на st(1), т.е. занимает определенное время. Поэтому и fsub+fstp может быть чуть быстрее чем наоборот, т.к.fsub изменяет только st(1), а fstp в данном случае работает с st(0) и st(2) и может выполняться параллельно с fsub.



    > "Если я делаю fstp st(2) вместо fxch st(1), следует ли в конце процедуры ставить fstp st или ffree st? С ffree быстрее."



    fstp st(2) и fxch st(1) эквивалентны с точки зрения результата, но fxch однозначно быстрее. И та и другая просто делают обмен st(0) и st(1) и оба из них остаются занятыми.



    fstp st и ffree st тоже эквивалентны с точки зрения результата - обе освобождают регистр st(0).

    Но что быстрее на практике однозначно сказать трудно. На P6 вроде и разницы быть не должно, а на P4 если есть, то в 1-2 такта. А если получается 4 тика, так это влияние дискретности RDTSC на P4 Norhtwood (а на Prescott'e CPUID = 15.3.x так вообще дискретность = 8). У меня например на P4 (CPUID 15.2.7) разницы не видно, а при определенных манипуляциях (вставкой nop'ов) можно получить что ffree получается "хуже" fstp на те же 4 тика ;)



    PS: Но теоретически ffree может быть несколько быстрее fstp по двум причинам. 1) у нее латентность вроде как чуть меньше (по данным А.Фога, хотя они относятся к первой тормознутой модели P4 но все же логичны, т.к. ffree не изменяет указатель вершины стека). 2) ffree не зависит от состояния st(0) и следовательно от предыдущих инструкций и может выполняться до их завершения. Никаких примечаний насчет особого смысла инструкции fstp st (как например xor r,r) в доках вроде как нет, поэтому можно предположить, что она имеет ложную зависимость по данным в st (как и sub r,r по сравнению с xor r,r) и должна ждать завершения предудщих инструкций, изменяющих st. Но это не означает, что реально время ожидания будет равно латентности предыдущей операции, т.к. здесь рулит не латентность, а througput, который м.б. гораздо меньше.

    И вообще это лишь предположения, а на практике, как я уже говорил, 1-4 тика для такого кода это мелочь, на которую не следует обращать внимания
     
  10. dr_dred

    dr_dred Сергей

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    301
    Адрес:
    Russia
    Спасибо, leo, все толково объяснил.



    rdtsc

    mov esi,eax

    invoke Sleep,1000

    rdtsc

    sub eax,esi



    делим eax на 1024^3 (FPU) чтобы получить частоту процессора в GHz (примерную). Сколько бы я не старался, не могу получить документированную частоту. Вообще я думал, что процессор чем больше загружен (taskmgr.exe), тем больше тактов он совершает (не больше, конечно, того, что может), ан нет. Написал короткую программу (p1), грузящую проц на все 100 (если верить taskmgr), запустил основную программу (p2), а результат примерно такой же (1.88 из 2 GHz).

    [​IMG] _1120102531__p.zip
     
  11. leo

    leo Active Member

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



    > "делим eax на 1024^3 (FPU)"



    И что получаем ? Правильно занижение частоты на 1.024^3 ~ 7%, т.к. делить нужно на 10^9. И еще для определения частоты неплохо было бы прогнать штук 6 циклов, 2 первых выбросить и 4 усреднить.



    > "Вообще я думал, что процессор чем больше загружен (taskmgr.exe), тем больше тактов он совершает"



    Ежели он не спит и ежели это не экономный mobile процессор, то rdtsc отсчитывает тики с частотой процессора независимо от его загрузки
     
  12. dr_dred

    dr_dred Сергей

    Публикаций:
    0
    Регистрация:
    12 мар 2005
    Сообщения:
    301
    Адрес:
    Russia
    Ах, ну да, здесь же не байты. Теперь все получается.



    Почему два первых выбросить (в кеше не разбираюсь, если речь об этом). Попробуй объяснить.



    p.s. Ты случайно не знаешь, что возвращает cpuid в первых четырех битах eax при входном eax==1? Написано Stepping ID, но описания нет (pdf от апреля этого года).
     
  13. leo

    leo Active Member

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



    > "Почему два первых выбросить (в кеше не разбираюсь, если речь об этом)"



    Да именно об этом. Грубо говоря при первом проходе процессор понятия не имеет, что ему придется прыгать на Sleep и ее код преспокойно лежит в ОЗУ. Когда он наконец "соображает", оказывается уже поздно и возникает большая задержка пока нужный код подгрузится из медленного ОЗУ в кеш. При последующих проходах код Sleep уже находится в кеше и извлекается быстро. Второй проход уже получается достаточно "точным", но все-таки как правило несколько завышен (по-видимому это связано с настройкой механизма предсказания переходов при наличии циклов и ветвлений).

    Вот если бы вы c Sonic'ом не броислись с ходу усреднять огромное число проходов, а просто выводили первых 8-10 замеров, то увидили бы что первое число на порядок завышено, а начиная с 3-го результаты практически одинаковые (за исключением HT+XP - там может быть просто каша)



    > Stepping ID



    Intel идентифицирует свои процы 3-мя числами: семейство (family),модель (model) и степпинг (stepping ID). Эти числа для своего проца можно посмотреть в виндовых сведениях о системе.

    Семейство определяет архитектуру процессора: 6 = P6 family (PII,PIII), 15 = P4. Модель как правило изменяется при изменении ядра процессора. Ну а степпинг - это модификация внутри данной модели (ядра), например более высокочастотные модели одного ядра имеют более высокий степпинг.
     
  14. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    cresta
    Значит ставим на него элипсную звёздочку, выставляем угол опережения педали :) и
    вместо исходных 283 такта на PII (266) и 430 тактов на Celeron (2,79) (первые три цикла не усредняются) имеем 249 на PII и 370 на Celeron.
    Код (Text):
    1. .data
    2.   qX    dq  35.7
    3.   qY    dq  7.25
    4.   t_tmp dt 1.0
    5.   i_3FFFh dd 3FFFh
    6. .data?
    7.   qZ    dq ?
    8. .code
    9.  
    10. FLD  QWORD PTR [qY]
    11. FLD  QWORD PTR [qX]
    12. FYL2X
    13. FLD ST(0)
    14. FRNDINT
    15. FXCH ST(1)
    16. FSUB ST(0), ST(1)
    17. FXCH ST(1)
    18. FIADD [i_3FFFh]
    19. FISTP WORD PTR [t_tmp + 8] ; почти FSCALE :)
    20. F2XM1   ; возведение в дробную часть степени
    21. FLD REAL10 PTR [t_tmp]  ; загружаем результат возведения в целую часть степени
    22. FXCH ST(1)
    23. FLD1
    24. FADDP ST(1), ST(0)
    25. FMULP ST(1), ST(0)  ; объединяем результаты
    26. FSTP [qZ] ; сохраняем и выкидываем
    Но самое забавное, что на 5 загрузок в стек имеем 4 выгрузки !!!
    И попытка добавить пятую генерит ошибку сопроцессора !!!, а так работает (тестировал FPU стек и на PII и на Celeron - результат одинаков!!!)

    Удалил глючный аттач, чтобы народ не смущать ;)
     
  15. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Y_Mur
    Ес-но, 4 явные выгрузки и 1 неявная в fyl2x - итого 5. Похоже ты не утруждаешь себя заглядывать в instruction reference ;) И лишних fxch опять натыкал, хотя после frndint можно было просто использовать fsub st(1),st(0) а после fld1 просто faddp st(2),st(0)
    И кстати, можно сделать чуть быстрее, если вместо fiadd + fistp добавить fist word ptr [..] сразу после FYL2X и add word ptr[..],3FFFh после F2XM1
     
  16. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    Увы обнаружил глюки в тестере и в итоге мой суперкод стал глухо проигрывать стандартному, но зато наконец накодил вроде бы путный тестер на основе советов wasm гуру, теперь можно и не доставать leo глупыми вопросами, а самому проверять ;)
    Это потому, что в 1:02:07, я больше так не буду :)

    совсем по мелочи подправил аттач
     
  17. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    Увы не получается :dntknw:
    leo
    после твоих советов ещё немного ускорить можно добавив после t_tmp заполнитель dw ? и заменив word ptr на dword ptr (правда ускорение только при выравненных данных)
    Но почему итоговая скорость оказывается намного хуже чем у стандартного алгоритма?
    Хотя если тупо выкидываешь FSCALE получаешь ускорение в 35 тиков (ес-но с не верным результатом), а целочисленная замена вдруг даёт проигрыш в аж в 70 тиков?
    (описанный в посте выше выигрыш результат глюков тестера)
     
  18. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    Ага всё таки сам разобрался :)
    На Celerone (2,8ГГц) задержка нааамного (проигрыш 748 тиков) больше чем на PII (266МГц, проигрыш 70 тиков) - значит дело в FPU - CPU совместном доступе к медленной памяти, и значит к совету Фога "везде где можно заменять медленную FSCALE целочисленным аналогом" следут подходить осторожно...
     
  19. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    Выложенный выше тестер не работает с лёгким кодом, его нужно усреднять по циклу заключённому между rdtsc

    Так, что лучше юзать два тестера:
    для лёгкого кода Speed_airili - не корректно показывает первый заход
    и для тяжёлого Speed_cowish - позволяет тестировать код до загрузки в КЭШ и ВТВ
    Код (Text):
    1.           airili |  cowish
    2.              std | std | new
    3. PII:         238 | 238 | 314
    4. Celeron:     277 | 308 | 1140
    5. std - стандартное возведение степень
    6. new - через целочисленное FSCALE
    На PII легкий и тяжёлый тестер дают одинаковый результат
    На Celerone а-ля P4, разница заметная, но я так понимаю главное не переносимость результата из тестера в тестер, а отзывчивость конкретного тестера на изменения в тестируемом алгоритме. А так попугаи, они попугаи и есть :)
     
  20. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Y_Mur
    Потому, что нужно быть внимательнее - у тебя t_tmp = 0, а не 1.0 - в результате после прибавления 3FFFh получается фиг знает какой NaN и замаскированное исключение FPU

    И с тестером чего-то ты намудрил ;)
    Во-первых, в результате rdtsc в eax - младшая часть, в edx - старшая, поэтому вычитание нужно делать sub eax,.. sbb edx,.. а не наоборот
    Во-вторых, непосредственно перед тестируемым куском нужно вставлять еще один cpuid - иначе этот кусок может выполняться не дожидаясь завершения предыдущего rdtsc
    В-третьих, подсчет оверхеда лучше делать один раз до основоного теста - также прогнать 5-6 раз и взять последнее значение.
    Ну и всякие лишние push\pop тут совершенно ни к чему - проще использовать переменные в .data?