Планирование потока инструкций

Тема в разделе "WASM.A&O", создана пользователем v_mirgorodsky, 7 авг 2006.

  1. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Возможно подобная тема уже обсуждалась на форуме, однако поиском найти ничего не удалось...

    Не секрет, что P4 и иже с ними подходят очень творчески к процессу выполнения команд. Однако для правильного задействования механизмов внеочередного выполнения команд они должны быть соответствующим образом спланированы и расположены в памяти компьютера.

    Из своего личного опыта - просто добавлением пары команд в тело цикла получилось увеличить его быстродействие на 40(!) тактов при общей продолжительности тела цикла порядка 200 тактов.

    В одной из книг Криса Касперски есть глава о Intel VTune версии 5.0. Там он описывает технику интерактивной оптимизации Ассемблерного листинга посредством изменения порядка следования инструкций на вход процессора. К сожалению во всех версиях VTune начиная с шестой этот инструмент отсутствует или я не знаю как его активировать.

    А как кто решает подобные вопросы оптимизации своего кода?

    С уважением,
    v_mirgorodsky
     
  2. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Читаем А.Фога и мануалы по оптимизации от Intel и AMD (к сожалению или к счастью все на английском :))
    Никакого особого творчества тут не требуется, кроме соблюдения некоторых элементарных правил
    Поделись ради интереса личным опытом - чего куда ты добавил, чтобы получить экономию 40 тиков из 200 - наверняка этому найдется объяснение и соответствующая рекомендация из мануалов
     
  3. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Ангера Фога я прочитал всего и очень вдумчиво, Intel Optimization Manual - это почти как настольная книга. Проблема в том, что ни один ни второй не дает однозначных рецептов оптимизации под архитектуру NetBurst. Более того - сам себе противоречит. Для примера возьмем операции с MMX регистрами. Судя по мануалу все MMX блоки подключены на оди и тот же порт - P2, если не ошибаюсь. Порт может пропускать через себя не более одной операции за такт. Таким образом имеем перекрытие только по операциям чтения/записи с алгоритмическими. Но, как показывает практика - это не правильно. P4 может выполнять до 3(!) MMX команд за такт если они направлены на разные исполнительные устройства. Что же тогда говорить о команде MOVD R32,MM. Заявленая латентность ее составляет 5 тактов, реально - 2-3 такта. Они честно об этом сообщают, но не указывают в каких случаях следует ожидать какой латентности. Те же проблемы с загрузкой констант из памяти и т.д. Подобных несуразиц могу привести достаточно много. Вот так и пробую оптимизировать порядок инструкций методом тыка. Часто бывает так, что последовательность противоречащая всем канонам оптимизации в конкретном месте выполняется с эффективностью порядка 0.4-0.5 тактов на команду, а бывает что абсолютно правильная последовательность команд тормозит. Все зависит от конкретного места в программе, загруженности кешей и фазы луны. Объясняется это наличием системы Replay. Где-то в Инете была статья на эту тему.

    Таким образом оптимизировать под P4 можно только будучи Intel Application Engineer или имея соответствующий софт под руками. Потому и возник вопрос - может кто-то нашел методы борьбы с Replay'ем ?
     
  4. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    М-да, крик души, да и только ;)
    "Из своего личного опыта" - обычно такие революционные заявления заканчиваются в стиле "у-пс, ошибочка вышла :dntknw:("
    Будем флудить в поисках друзей по несчастью или все-таки попробуем разобраться на конкретных примерах ? Хотелось бы взглянуть на чем основываются лозунги "40 из 200", "3 MMX за такт" и "2-3 такта вместо 5" ;)

    PS:
    1) операции пересылки MMX осуществляются блоком FP_MOV через порт P0, остальные через порт P1, поэтому частичное распараллеливание возможно
    2) чтобы нарваться на реплэй нужно хорошо "постараться", поэтому и "метод борьбы с ним очень простой - не наступать на грабли
     
  5. asmfan

    asmfan New Member

    Публикаций:
    0
    Регистрация:
    10 июл 2006
    Сообщения:
    1.004
    Адрес:
    Abaddon
    Reciprocal throughoutput (если правильно написал:) вот это вам объяснение увеличения скорости выполнения...
    1. Avoid dependecy chains
    2. Нагружай одновременно разные пайпы, 4-1-1 (до Конро, для оного по другому, сейчас точно не вспомню) всё ещё действует.
    3. ту би континуед...)
     
  6. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    asmfan
    Тут вроде о NetBurst "базар" идет, а не о P6 :)))
    К тому же не стоит впадать в крайности - не все так сложно и непредсказуемо, но и не столь тривиально - подводные камушки все же встречаются

    Раз автор не торопится приводить примеры, приведу свой примерчик (из сохранившихся :) и там же - его правдоподобное объяснение
     
  7. asmfan

    asmfan New Member

    Публикаций:
    0
    Регистрация:
    10 июл 2006
    Сообщения:
    1.004
    Адрес:
    Abaddon
    Собственно говоря NetBurst - это недоразвитая многоядерность. Поэтому оптимизировать не под НетБёрст, а под многоядерность надо. Понятие масштабируемость уместно. Вот пример от Интела
    :: e = a + b + c + d ::
    e = a + b
    c = c + d
    e = e + c
    No dependecy chain -> parallelizm.
    P.S. P6 = Conroe
     
  8. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Автор не не торопится, а просто не может ;) Я не зря сказал, что поведение сильно зависит от конкретного местоположения фрагмента кода в контексте всего приложения. Выше означенный цикл занимался предварительной упаковкой данных перед энтропийным кодированием. Вышеозначенная последовательность занималась следующим:
    Код (Text):
    1.             ;; Bits = SrcBits & ((1 << NumValidSrcBits) - 1);
    2.             mov         eax,1
    3.             shl         eax,cl
    4.             sub         eax,1
    5.             and         ebx,eax
    Почему она приводила к экономии 40 тактов на 200 - точный ответ остается загадкой. В конце поста я привел некоторые соображения по этому поводу, однако я не могу сказать, что они правильны. В другом месте подобный цикл удлиннялся вышеозначенной последовательностью на 1.7-2 такта, что и ожидалось.

    По поводу MMX предлагаю проекспериментировать с очень интересной последовательностью и попытаться ее объяснить:
    Код (Text):
    1.             rdtsc
    2.             mov         StartTicks,eax
    3.  
    4.             pcmpeqb     mm1,mm1
    5.             pcmpeqb     mm3,mm3
    6.             pcmpeqb     mm5,mm5
    7.  
    8.             movq            mm0,St0
    9.             movq            mm2,St2
    10.             movq            mm4,St4
    11.  
    12.             paddw       mm0,mm1
    13.             psubw       mm2,mm3
    14.             paddw       mm4,mm5
    15.  
    16.             movq            St0,mm0
    17.             movq            St2,mm2
    18.             movq            St4,mm4
    19.  
    20.             rdtsc
    21.             mov         EndTicks,eax
    Цикл выполняется 100000 раз, на каждой итерации вичысляется длительность, из результатов по длительности выбирается самый минимальный. На моей машине повторяемость результатов абсолютная. К экспериментам предлагается добавление в тело цикла дополнительных триплетов и интерпретация полученных результатов. Исходя из Фога и Intel Optimization Manual последовательность должна выполняться со скоростью 1 такт на команду.

    Теперь немного о том, что получается у меня на машине. Две последовательные RDTSC имеют задержку порядка 97 тактов. На моем компьютере 25 триплетов занимают тех же 97 тактов, 26 триплетов - 105 тактов, 27 триплетов - 105 тактов, 28 триплетов - 112 тактов. И так далее. Функция количества тактов затраченных на выполнения кода от его фактической длинны имеет сильно нерегулярный характер. И, да, при определенных количествах УВЕЛИЧЕНИЕ длинны последовательности приводит к УМЕНЬШЕНИЮ количества тактов, затраченных на выполнение.

    М-да, ну и как здесь избежать наступания на грабли? Правильно, необходимо включить алгоритм функционирования риплея (граблей) в общую стратегию планирования потока инструкций. В NetBurst риплей является не фичей, которую можно обойти грамотным планированием, а одним из основных исполнительных блоков, обеспечивающих нормальное функционирование процессора при исполнении ним потока инструкций вне порядка их следования в тексте программы. Иногда эта фича помогает процессору не терять такты на ожидание результатов длительных операций, иногда приводит к тормозам в программе, где их быть не должно в принципе.

    Теперь немного о 200 тактовом цикле и моих соображениях. Если условие перехода по циклу может быть вычислено вне очереди по отношению к телу цикла, то процессор не будет заниматься предсказанием по окончании тела цикла, он просто перейдет по нужному адресу. В моем случае, по видимому, добавление пяти инструкций вкупе с реплеем позволило процессору избежать лишних штрафов за неправильные предсказания переходов.
     
  9. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    v_mirgorodsky
    Если:
    то вероятность реплея близка к нулю, как мне кажется...
     
  10. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Прочитав энное количество мегабайт по вопросу у меня сложилось следующее представление о функционировании NetBurst. Пусть на текущий момент на выполнении находится некая длительная команда, результат которой будет доступен через несколько тактов и пусть результат выполнения следующей команды зависит от результата этой. Что сделает процессор? Будет ждать результата выполнения этой команды, поскольку они зависимы между собой? Так бы сделал PIII или PM. А P4 немедленно начнет выполнять следующую команду как только освободится устройство ее выполнения. По окончании выполнения второй команды он таки увидит, что вместо ожидаемого результата он посчитал лабуду и принудительно ОТПРАВИТ вторую команду на Replay. Далее завершится первая команда, с которой все началось, а вторая прокрутившись через Replay достигнет устройства выполнения и посчитает свой результат. Таким образом, P4 не останавливается. Он все время что-то исполняет. При этом даже хорошо спланированный свой код не гарантирует отсутствия Replay, поскольку управление в "свой" контекст может вернуться в любом месте при переключении контекста.

    В теории способ борьбы с Replay'ем очень прост - надо держать execution units постоянно нагруженными правильной работой. Проблема только в том, что очень сложно отследить занятость того или иного execution units в конкретный момент времени. А если Replay все же возник, то как учесть где он закончится? Потому и пытаюсь найти некий оптимизатор, который мог бы хотя бы показывать текущий статус исполнительных блоков по анализу исходных текстов. Имея эту информацию можно было бы что-то планировать.

    К стати, правильно спланированная программа под PIII выполняется всего лишь удовлетворительно на P4. Проверено на Intel Application Note 922. Там обсуждается реализация быстрого дискретного коссинусного преобразования. Код, спланированный под PIII занимает 250 тактов процессора, тот же код на P4 исполняется 450+ тактов. Для PIII приходится порядка 0.6-0.7 такта на команду, для P4 - 0.94.
     
  11. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    v_mirgorodsky
    Насколько я уяснил из мануалов - реплей может возникать только при чтении операнда из памяти, в таком случае планировщик закладывается на то, что операнд находится в L1 cache. А вот если он не в L1 - только тогда микрооперация отправляется на реплей. По идее, при работе в регистрах шедулер уже знает, когда будет готов операнд и реплей возникать не должен.
    В принципе, можно точно проверить код на возникновение реплея через счетчики производительности. Я как раз морочу себе этим мозги, правда до P4 пока не добрался, долбаю AMD64 на тему branch prediction - У Фога интересная заметка на счет того, что оно валится чаще, чем должно бы по теории, но что-то у меня не клеится...

    Ну так у PIII конвейер-то раза в два покороче, чем у P4 и latency поменьше...
     
  12. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    v_mirgorodsky
    P6 = {P-Pro, PII, PIII, Pentium M, Core, Core 2}
    ИМХО, это просто-напросто недокументированная фича P4 - на Northwood'ах RDTSC выдает результаты с дискретом 4 тика, а на старших моделях - 8 тиков. Если каждый триплет выполняется около 3 тактов, то и получается что перескок на новый дискрет происходит на каждом третьем триплете. Плюс возможны отклонения, связанные с выравниванием, отставкой, переполнением очереди и т.п.

    В корне неверное, но достаточно распространенное заблуждение (см. например Предсказания переходов того или иного процессора). Предсказание осуществляется на первых стадииях конвеера (TC_next_IP), а не во время или после исполнения. Одновременно с загрузкой 6-ки мопов, содержащей jcc, BTB выдает решение о том какую 6-ку нужно грузить следующей чтобы не было простоя конвеера. А до исполнительного устройства jcc доберется в лучшем случае только через пару десятков тактов (на P4E и того больше - задержка = длине misprediction-pipeline минус число начальных стадий TC_next_IP и минус несколько финальных стадий коррекции BTB). Поэтому условные переходы всегда выполняются спекулятивно и в случае ошибки предсказания осуществляется сброс конвеера.

    Чушь чистейшей воды, навеянная неверной интерпретацией завораживающих мультиков от Ф-центра ;))
    Во-первых, как заметил Ustus классический реплэй может возникать только при чтении операндов из памяти. Во всех остальных случаях процессор либо "точно" знает время исполнения команд и в соответствии с этим планирует последующие запуски (основная часть простых вычислительных операций), либо действительно держит зависимые мопы до завершения длительных плохопредсказуемых операций, для которых несколько стадий конвеера от планировщика до исполнительного блока - это мелочь по сравнению с общей латентностью. А внеочередное исполнение и придумано для того чтобы во время простоя одних мопов исполнять другие независимые мопы и не гонять попусту одну и ту же команду, пока наконец не получится верный результат :))

    Во-вторых, реплэй реплэю рознь. Есть оправданный реплэй, обусловленный отсутствием данных в L1. В данном случае по любому возникает приличная задержка, поэтому по большому счету пофиг чем в это время заниается процессор - придерживает зависимые мопы или гоняет их по петле реплэя. А есть дебильные реплэи, обусловленные граблями и колдобинами на пути покорения сверхчастот. То, что это откровенные грабли Интел неявно признает в мануалах по оптимизации и явно указывает на недостатки предыдущих версий, когда пиарит очередной продвинутый "шедевр". Сюда относятся частичная проверка L1-хит по минитегу, частичная проверка адреса при store-to-load forwarding'е и апофеоз тупости первых моделей P4 - отсутствие каких либо проверок зависимости load от предыдущих store. Вот дебильный пример дебильного реплэя на Northwood:
    Код (Text):
    1.   xor ecx,ecx
    2.   push ecx
    3.   add ecx,1000
    4. ;align 16
    5. @@:
    6.   mov eax,[esp]  ;<-- 100% реплэй на P4 model 2 (Northwood)
    7.   add eax,ecx
    8.   mov [esp],eax
    9.   sub ecx,1
    10.   jnz @B
    11.   add esp,4
    Загрузка eax в начале цикла выполняется спекулятивно - раньше, чем предыдщее сохранение. В результате получаем реплей с задержкой до 12-15 тиков в пересчете на итерацию (зависит от выравнивания !?).
    > "М-да, ну и как здесь избежать наступания на грабли?" ;) Т.к. инструкций мало, то их перестановка тут мало что дает и приходится вводить дополнительную задержку перед загрузкой. Добавление пары нопов устраняет реплэй и задержка становится 3 тика на итерацию. Другой способ устранения реплэя - это ввести зависимость операции загрузки от сохраняемого значения, например так
    Код (Text):
    1.   and eax,0  ;mov eax,0 и xor\sub eax,eax тут не катят, т.к. P4 считает их независимыми от пред.знач.EAX
    2.   mov eax,[esp+eax]
    В результате имеем те же 3 тика. Ну а лучше всего вообще обходить эти грабли стороной - если заменить этот дебильный код нормальным add [esp],ecx то получим 2 тика на итерацию и никаких проблем с реплэем.
    Теперь посмотрим как с этим кодом справляется продвинутый P4 model 3 Prescott, у которого введен анализ зависимости load от store. Справляется "прекрасно" - никакого реплэя, вот только задержка на итерацию получается 6 тиков, причем "стоит колом" независимо от выравнивания, добавления разумного числа нопов и даже при замене трех инструкций на одну add [esp],eax. Спрашивается за что боролись, если тупой Northwood 3ГГц при соблюдении элементарных кодинг-рулов отрабатывает этот цикл в 2-3 раза быстрее любого продвинутого Prescott'а ?!!!
    Вывод - реплэй это не фича, а зло, с которым Интел сами боролись даже ценой снижения общей производительности, а в итоге похоронили эту идею вместе с NetBurst. Вот только интересно, какие новые (или старые) грабли они заложили в Core 2 ;)
     
  13. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Ладно, по поводу условных переходов - может я и ошибся. Простотакое впечатление сложилось по коду. Цикл с 16 итерациями и со статической переменной в качестве счетчика цикла вел себя на удивление хорошо и давал очень мало Branch Missperdictions Events при профилировке. Потому и возникло впечатление, что переход по циклу вычисляется вне очереди, потому как по входу в цикл количество итераций каждый раз было разным. По другому объяснить это я не смог, потому и возникла такая теория.

    Теперь по поводу триплетов MMX инструкций. Я знаю о дискретности RDTSC. Я не поленился и написал небольшую прогу, которая генерила последовательность инструкций, исполняла их по 100000 раз и строила график зависимости количества триплетов и времени исполнения. Согласитесь, ничто не могло бы УМЕНЬШИТЬ время выполнения последовательности при УВЕЛИЧЕНИИ длинны самой последовательности. Однако это имеет место быть. Риплей отсутствует, архитектура строго детерминирована, процессор делает то, что от него ожидают, результат выполнеия верен, длинна последовательности увеличивается, а время выполнения уменьшается. Ваш ход, Маэстро ;)

    А теперь ответ на тему реплая цитатой из Intel Optimization Manual. In order to maximize performance for the common case, the Intel NetBurst microarchitecture sometimes aggressively schedules μops for execution before all the conditions for correct execution are guaranteed to be satisfied. In the event that all of these conditions are not satisfied, μops must be reissued. This mechanism is called replay. Some occurrences of replays are caused by cache misses, dependence violations (for example, store forwarding problems), and unforeseen resource constraints. In normal operation, some number of replays are common and unavoidable. An excessive number of replays indicate that there is a performance problem.

    Переводить не буду, поскольку все грамотные ;) Ключевая фраза - "unforeseen resource constraints" - неожиданные ограничения по ресурсам. В своей реальной жизни я еще и электронщик. Я знаю как строить микропроцессорные системы и могу догадываться о внутренней структуре блоков и причинах, вызывающих недетерминированные задержки в исполнении. Так вот, выше приведенная фраза говорит либо о некратных рабочих частотах pipeline и execution core, либо о внеочередном приоритетном исполнении инструкций, приходящих со стадии реплея, out-of-order buffer или еще откуда-нибудь. Второе утверждение более вероятно. Оно косвенно подтверждается в главе "Intel NetBurst Microarchitecture". Далее есть еще более интересное утверждение: "The microarchitecture executes instructions dynamically and out-of-order, so the time it takes to execute each individual instruction is not always deterministic". Из контекста следует убрать слово "always", тогда картина будет более полной. Вы поймите, для внеочередного выполнения команды на уровне проводочков и транзисторов необходимо реально переключить некий мультиплексор и взять этот μops из совершенно другого места. Плюс, этим мультиплексором необходимо управлять. Некая логика должна проанализировать возможность выполнения конкретного μops и отправить его на выполнение. А если несколько μops готовы к выполнению, какой из них выполнится раньше? В литературе я встречал утверждение о размере out-of-order buffer на ~80 μops. Я сомневаюсь, что все 80 одновременно анализируются на предмет возможности исполнения, однако могу однозначно утверждать, что анализируется не один. Ну и какие простые правила программирования помогут помочь в анализе такого контекста? Ответ прост и находится в моем предыдущем посте - надо включить логику функционирования реплея и out-of-order буффера в логику планирования инструкций. Тогда на больших кусках можно получить выигрыш по скорости выполнения кода. К сожалению, информации о логике функционирования этих блоков нет.

    Респект, именно так и я думаю по этому поводу ;)
     
  14. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    И грамотные, и ушлые и не пытаемся искать тайный смысл в словах, написанных на заборе ;))
    Ты выдернул цитату из раздела терминологии performance metrics, а теперь загляни в раздел Overview of the Intel NetBurst Microarchitecture Pipeline, где черным по белому написано:
    1) "The out-of-order core aggressively reorders µops so that µops whose inputs are ready (and have execution resources available) can execute as soon as possible" (стр. 1-9)
    2) "Finally, µops can begin execution out of program order, as soon as their data inputs are ready and resources are available" (стр.1-12)

    (Кстати в ранних редакциях IA-32, volume 1 была точно такая же фраза, но начиналась она не с Finally, а c Note that, но в последних версиях ее почему то вообще убрали или потеряли ;)

    Можно конечно начать дискуссию о тайных смыслах глагола "can", но ведь кроме общих слов в мануалах, писанных фиг знает кем, как и для кого, еще есть статьи в Intel Technology Journal (ITJ) и т.п. Вот пример из статьи The Microarchitecture of the Intel® Pentium® 4 Processor on 90nm Technology (ITJ, Volume 8, Issue 1, 2004):
    The out-of-order core will schedule for execution as many ready uops as possible each clock cycle, regardless of their original program order. By considering a larger number of uops from the program, the out-of-order core can usually find many independent uops that are ready to execute. The maximum number of uops that the out-of-order core can contain is 126, of which 48 can be load operations and 32 can be store operations.
    At the heart of the out-of-order core are the uop schedulers. The schedulers determine when a uop is ready to execute by tracking its input register operands. When the input operands have been produced, the uop is considered to be ready to execute. The scheduler will then schedule the uop to execute when the execution resources required by the uop are available. Thus, uops are allowed to schedule and execute in what is called data-dependent order. In many code sequences, there are independent streams of execution. The scheduler identifies the streams of execution and allows these streams to execute in parallel with each other, regardless of their original program order.
    There are five different schedulers connected to four different dispatch ports.

    Уже понятнее, но опять таки не совсем точно. Ведь мы люди "грамотные" и понимаем, что мопы должны пройти несколько стадий конвеера (уж никак не меньше одной) от планировщика до выхода исполнительного блока, когда им будет присвоен статус ready. Поэтому быстрые операции не могут ждать полного подтверждения готовности операндов и планировщик должен выпускать их с некоторым упреждением. Поэтому правильнее говорить не о готовности операндов, а готовности выпуска мопа. Поскольку достоверной инфы на этот счет нет, то позволю себе пофантазировать. Например, если один планировщих выпускает моп fast-ALU, то он может сразу установить ему флаг готовности выпуска зависимых операций. Для мопов с фиксированной, но не слишком большой латентностью (например < 16), декодер или микрокод-ROM могут задавать необходимое число тактов ожидания в самом мопе - тогда планировщик может после выпуска просто ставить такие мопы в особую очередь, декрементируя на каждом такте счетчики всех мопов этой очереди, а перенос при обнулении счетчика и будет флагом готовности выпуска зависимых операций и извлечения данного мопа из очереди ожидания. Для длительных и непредсказуемых операций, как я уже говорил, смысла считать такты нет, поэтому для их обработки достаточно добавить флаг запрета декремента счетчика (или запрета записи переноса), а для разрешения выпуска зависимых мопов ес-но использовать OR с флагом полной готовности, который устанавливается по окончании выполнения операции. Для "специалистов в электронике" вроде все выглядит достаточно "тривиально", поэтому рисовать схемы думаю смысла не имеет ;)

    Во-первых, реплэй возникает только тогда, когда моп уже вышел из планировщика, но получил неверные операнды. "Приходящие откуда-нибудь" мопы могут лишь задержать выпуск других мопов, но не могут повлиять на уже выпущенные, поэтому это предположение неверно.
    Во-вторых, речь может идти о нехватке load- (48 штук) или store- (24 или 32 в завис-ти от модели) или WC-буферов (6 или 8), которые при вкл. HT еще делятся пополам между потоками. Это и есть unforeseen - расчитывали на нормальную типовую прогу, а попалась экзотическая или дебильная. К примеру можно за(ш)кодить подряд пару делений, а затем цикл обнуления блока памяти, который ес-но быстро стопорнется из-за нехватки store-буферов и будет ожидать завершения деления и массового похода ожидающих мопов записи в отставку.
    А вот представить себе непредсказуемые задержки в правильно спроектированных логических схемах, тем более имея опыт в электронике - это из области фантастики или больного воображения :) Разумеется каждый мукс имеет определенную задержку, но все эти задержки учитываются при выборе тактовой частоты CPU и разбивке конвеера на стадии. Например в PIII на планирование и запуск отводится всего 2-3 стадии, в P4 Northwood 7-8 стадий, а в старших моделях еще больше. В PIII один загадочный планировщик под названием RS, а в P4 их пять и каждый обрабатывает не более 8-16 мопов предварительно отсортированных по типу (см. например статейку "Последний из могикан" на Ф-центре). Так что ни о какой каше из "~80 uops" речи не идет - за несколько стадий все сортируется и раскладывется по полочкам (в итоге планировщики получаются не сложнее, чем в "дубовых" AMD K7\K8). О возможном варианте обработки флагов готовности я сказал выше. "А если несколько uops готовы к выполнению, какой из них выполнится раньше?" Вроде как во всех архитектурах действует принцип FIFO - каждый планировщик выбирает первый моп из готовых к исполнению. Тут правда Intel'ы по сравнению с AMD намудрили с портами, поэтому требуется дополнительная схема выбора, когда два планировщика пытаются одновременно запустить мопы в один порт - но "хозяин-барин", значит им так нравится :))

    Опять пытаемся искать тайный смысл и тянуть одеяло в свою сторону ;))
    Речь идет не о мопе, а об инструкции x86, которая может состоять из нескольких мопов. Например, add m,r состоит из 3-х мопов, которые за счет динамического исполнения могут выполниться не друг за другом, а размазаться во времени фиг знает на сколько - вот тебе и "not deterministic", и это приходится по сто раз объяснять любителям подсчета "числа тактов на команду"

    Вывод: ты чересчур преувеличиваешь проблему реплея. Если не считать кэш-промахов, которые сами по себе приводят к существенной задержке, то все остальное можно считать "граблями", которые можно обойти при соблюдении элементарных кодинг-рулов, в том числе и известных ограничений store-to-load forwarding'а и алиасинга L1
     
  15. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    v_mirgorodsky
    Проверил - риплей присутствует даже на хваленых Prescott'ах, не говоря уже о Northwood'ах. Убери грабли (например, измени адреса чтения и записи данных), и все станет уныло монотонно и предсказуемо ;) И скорость возрастет до 2-х инструкций за такт, т.к. арифметика будет выполняется параллельно с чтением\записью
     
  16. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Конечно, можно говорить о любителях считать такты. Но я их считаю потому, что имею очень жесткие требования по быстродействию системы обработки. По ТЗ надо обработать порядка 400МБ данных в секунду, а пока получается всего 300 с лишним. В своих внутренних циклах я даже элементарные push/pop использовать не могу, потому как это сразу "откусывает" пару-тройку процентов производительности.

    Вот кусочек кода из живого обработчика. Объяснить его ненормальное поведение я не смог. Все выполняется в пределах того самого 100000 итерационного цикла, измеряется самое минимальное время и далее по тексту. За один раз заполняются все MMX регистры, затем сохраняются в тоже место, откуда были взяты отдельные word'ы. Думаю говорить, что все выровнено по кеш-линиям будет лишним. Кеш-лайн сплитс и тому подобная фигня исключена. Store-to-load forwarding отсутствует.

    Код (Text):
    1.         pinsrw      mm2,word ptr [esi],0
    2.         pinsrw      mm2,word ptr [esi + 8],1
    3.         pinsrw      mm2,word ptr [esi + 16],2
    4.         pinsrw      mm2,word ptr [esi + 24],3
    По идее, все должно спайплайниться и выполняться со скоростью 1 такт на слово - pinsrw throughput - 1 такт, latency - 4 такта. Сейчас не вспомню точных цифр, но было что-то около 4 тактов на слово. Перекодил цикл так:

    Код (Text):
    1.         movzx       eax,word ptr [esi]
    2.         pinsrw      mm2,eax,0
    3.         movzx       eax,word ptr [esi + 8]
    4.         pinsrw      mm2,eax,1
    5.         movzx       eax,word ptr [esi + 16]
    6.         pinsrw      mm2,eax,2
    7.         movzx       eax,word ptr [esi + 24]
    8.         pinsrw      mm2,eax,3
    Второй кусок кода ощутимо быстрее - где-то 3 такта на слово. По мануалу скорость первого куска должна быть эквивалентна или выше чем второго - uop'ов меньше. По мануалу должно тратиться не более 64 тактов на 64 слова, а тратится более 200. По мануалу использование разных регистров на месте eax должно облегчать задачу процессору, а на деле не оказывает на него никакого влияния. По мануалу предварительный pxor на все MMX регистры должен был бы ускорить код, поскольку устранил бы его зависимость с предыдущим контекстом, на деле pxor'ы просто не оказывают никакого влияния на производительность. Казалось бы, вычитка двойными словами должна бы уменьшить нагрузку на память и увеличить скорость, однако ничего такого не происходит. Пытался интерливить четыре регистра между собой - никаких изменений. Могу перечислять дальше, поскольку провел над ним не один десяток разных экспериментов. Реальное поведение процессора мало коррелирует с тем, что написано в мануале :dntknw:

    Ну и как это объяснить? Вот и давайте обсудим "соблюдение элементарных кодинг-рулов, в том числе и известных ограничений store-to-load forwarding'а и алиасинга L1" в приложении к реальному кусочку кода.

    К слову, во время профиллировки VTune не находит в тексте всего обработчика ни одной проблемы. Просто показывает, как в средине ни в чем не повинного куска такты начинают уходить в никуда, при этом показания остальных счетчиков не демонтрируют никаких аномалий. Вот после этого и начинаешь пытаться читать мануалы между строчек.
     
  17. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Э не, я проверял только арифметику. Синтерливить запись/чтение/вычисления я тоже могу - все получается хорошо - 1.9 команды на такт, если верить VTune. Изначально цикл состоял вообще только из арифметических триплетов и именно их я таймил. Именно с ними возникают проблемы. При увеличении длинны цикла только арифметическими операциями получается увеличение времени исполнения его с коеффициентом около 3.6-3.7 за исключением неких аномалий, когда время выполнения цикла уменьшалось по сравнению с предыдущим шагом. Это означает что на чисто арифметических операциях процессор тратит еще 0.6 такта на каждые три. Зачем? Почему? По мануалу арифметика имеет задержку 1 такт и пропускную способность 1 такт. Зачем ему тратить еще 0.6? Я увеличивал последовательность еще одной арифметической командой над еще двумя регистрами. Ситуация становилась только хуже.

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

    Вот и верь после этого людям ... (С)
     
  18. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Предисловие:
    Эпиграф:
    Краткое содержание 3-й серии с подстрочным переводом:
    throughput - 2 такта
    latency - 3-4 такта для r32, для m16 как минимум на 2-3 такта больше, т.е. >= 6 тактов

    Прописная истина - в PPro-PIII и P4 число мопов (mov r,m)+(op r,r) и (op r,m) - одинаково, т.к. от мопа load никуда не деться. Только в продвинутых P6+ и AMD на стадии декодирования используется uop-fusion - слияние мопов op+load в один макрооп
    В P4 первый кусок только начальное декодирование проходит быстрее (но это роли не играет, т.к. исполнение все равно идет медленнее), а при использовании в цикле мопы извлекаются из Т-кэша и разницы по числу и скорости доставки мопов для рассматриваемых кусков - нет.

    Возможно дедушке i486 это бы и помогло, а в современных процессорах рулит переименование регистров - основа основ Out-of-Order execution

    В P4 нет проблемы partial register stall, свойственной P6-. Поэтому pxor лишь устраняет зависимость от предыдущих операций с регистром и позволяет начать следующую операцию, не дожидаясь завершения предыдущей. Если предыдущая операция "давным-давно" завершилась, то дополнительный pxor в лучшем случае бесполезен, а в общем случае - вреден, т.к. попусту занимает ресурсы, мешая продвижению других мопов.
    "it can be useful to clear the content and break the dependence chain by ... using the pxor" - смотрим в книгу, а видим фигу - между строк ;))

    Данные читаются из ОЗУ в L2 по 128 байт, а из L2 в L1 по 64. Время доставки ворда и дворда из L1 одинаково, поэтому если мы работаем с двордами, то ес-но и читаем дворды. А если нужны ворды, то все зависит от конкретной задачи - нужно учитывать латентности операций преобразования и загрузку портов. В данной ситуации чтение двордами в лучшем случае ничего не дает на P4 model > 2 и может быть заметно хуже на model <=2, в которых shr выполняется 4 такта на MMX_SHIFT и конкурирует c pinsrw за порт p1, в то время как порт p2 (load) работает не с полной загрузкой

    Это говорит лишь о том, что планировщики и сами неплохо справляются с переупорядочиванием операций - Out-of-Order рулит ;))

    Для начала не мешало бы сами строчки повнимательнее прочитать ;))))))))

    PS: А со своими MMX-триплетами ты уже достал ;) Одни общие слова и домысливания. Или приводи конкретный код с конкретными вопросами или наслаждайся в одиночестве своим собственным мнением. Смысл куска с pinsrw тоже не понятен - если это просто перестановка вордов (все первые, затем все вторые и т.д.), то возможно было бы быстрее на АЛУ сделать без всяких MMX.
    PPS: А вообще, следует заметить, что тонкая однопоточная оптимизация под P4 уже давно не актуальна - повсеместно рулит дебильный HT, сводящий все усилия на нет. Судя по твоей странной методике тестирования ты видать тоже под HT пытаешься блох ловить ;))
     
  19. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Х-м-м-м, похоже, мы разные мануалы читаем ;) "IA-32 Intel® Architecture Optimization Reference Manual", Order Number: 248966-013US, April 2006 - Table C-5 Streaming SIMD Extension 64-bit Integer Instructions - пятая строка сверху - PINSRW mm, r32, imm8 => throughput - 1 такт, latency - 4 такта. Кто из нас не умеет читать мануалы?

    Хорошо, пусть есть вышеприведенный поток команд. Как видимо все разбиваются на два (?) независимых uop'а. Один уходит на порт 2 - memory load, второй на порт 1 - MMX_SHIFT. Обе операции могут выполняться параллельно. Общая продолжительность операции в пайплайне (если я правильно понимаю пайплайн) должна составлять 1 такт на слово, поскольку операции должны перекрываться. Я допускаю, что собственно загрузка в регистр eax значения с шины порта 2 требует еще одного uop, но все они должны выполняться параллельно, превращая процессор в эффективный аппарат по пересылке данных только в пределах L1 кеша, что можно гарантировать малым размером данных - 128 байт и малым размером кода - 64 movzx, 64 pinsrw, 16 movq. Где ошибка в рассуждениях? Буду очень благодарен за пояснение моих ошибок.

    Еще вопрос - похоже я не правильно понимаю dependence chain. Мне казалось, что по окончании latency никакой зависимости между предыдущей и последующей операцией быть не должно? Я не прав?

    HT я запретил в BIOS и наслаждаюсь нормальной однопроцессорной конфигурацией.
     
  20. v_mirgorodsky

    v_mirgorodsky New Member

    Публикаций:
    0
    Регистрация:
    7 авг 2006
    Сообщения:
    53
    Спасибо - "число мопов (mov r,m)+(op r,r) и (op r,m) - одинаково" - этого не знал. Кусок с чистыми pinsrw работает значительно медленнее. Я писал результаты тестирования. Почему - это еще один вопрос. К стати в мануале есть совет использовать чистый pinsrw и аргументировано тем, что это позволяет процессору использовать скрытые регистры, недоступные для программиста или компилятора.