leo, я знаком с архитектурой P6. И знаю про динамическое исполнение команд, и про влияние последовательности инструкций на производительность. Просто мне стало интересно, насколько изменилось количество тактов на команды по сравнению с Pentium/Pentium 2 (о них инфа имеется), поэтому я и обратился в форум за помощью. Изучая эволюцию процессоров, можно заметить, что на некоторые команды количество тактов уменьшилось, а на какие-то увеличилось в разных моделях процессоров. От этого зависит какими командами пользоваться предпочтительнее. И т.к. первый Pentium уже не актуален, я решил найти более свежую информацию . Хочу кое что у тебя уточнить ). Про трейс-кэш я с тобой полностью согласен. А вот про запись в секцию кода на расстоянии менее 1Кб от исполняемого куска немного сомневаюсь. Я скажу мои мысли, если я не прав, поправь меня: Запись в сегмент кода в защищенном режиме запрещена (т.е. с использованием префикса cs). Код можно изменить, если только воспользоваться сегментами данных, пересекающимися с сегментом кода. Т.е. процессор даже не узнает, что мы пишем в код, т.к. это будет выглядеть как запись в сегмент данных. bogrus, можно у тебя кое что уточнить ? Latency - это задержка во времени, а throughput - производительность. Ты написал: imul r,r,i - одномопная инструкция, выполняется в порту 0 (ALU), её latency - 4 такта, throughput - 1 такт Далее: imul ecx,ecx,5 ; 1 imul edx,ecx,5 ; 1 imul ecx,ecx,5 ; 4 imul edx,ecx,5 ; 2 4 такта - это ожидание второй интрукции, пока выполняется первая. Т.е. с момента выполнения первой инструкции, до начала выполнения второй проходит 4 такта ? Время выполнения первой входит в эти 4 такта ? А время выполнения второй суммируем отдельно, или тоже запишем в эти 4 такта ? Если все отдельно, то сумма тактов первого столбца подсчитана немного некорректно ? Что ты думаешь по этому поводу ?
Hunter А черт его знает, я сам ещё толком не разобрался, понимаешь, если за imul поставить зависимую операцию, которая использует 1-й порт, то результат в том же такте может перейти в этот блок для вычисления, тогда потери может и не быть, а в других случаях возможно следующей инструкции прийдется ждать полного вывода результата Во-первых latency - 4 я взял не от фонаря, эти данные есть у Фога, есть и у Intel, вот цитата для нескольких видов imul: Код (Text): HDR: "IMUL r16/32,rm16/32,imm8/16/32": ( 011010.s.1 11.ddd.sss -------- ) 1 FLOW: REG_ddd = int_mul.Port_0.Latency_4(REG_sss, IMM) HDR: "IMUL m32": ( 1111011.1 00.101.MMM -------- ) HDR: ( 1111011.1 01.101.MMM -------- ) HDR: ( 1111011.1 10.101.MMM -------- ) 1 FLOW: TMP1 = load.Port_2.latency_1(MEM) 2 FLOW: TMP0 = int_mul.Port_0.Latency_4(EAX, TMP1) 3 FLOW: EDX = Port_0.latency_1(TMP0, CONST) 4 FLOW: EAX = move.Port_01.latency_1(TMP0) А во-вторых я привел результаты RDTSC замера
Hunter > "Запись в сегмент кода в защищенном режиме запрещена (т.е. с использованием префикса cs). Код можно изменить, если только воспользоваться сегментами данных, пересекающимися с сегментом кода." В плоской (flat) модели памяти именно такую ситуацию мы и имеем. Поэтому для записи в сегмент кода достаточно объявить секцию кода writeble или использовать VirtualProtect в runtime и пиши наздоровье обычным mov как обычные данные (если я не прав, поправь меня . > "Т.е. процессор даже не узнает, что мы пишем в код, т.к. это будет выглядеть как запись в сегмент данных" Даже в сегментированной модели процессор все узнает, т.к. он контролирует запись не по логическим, а по линейным адресам. Подробности см. IA-32, ч.3, раздел 7.1.3. Handling Self- and Cross-Modifying Code А то, что на P4 возникают дополнительные задержки при записи данных, расположенных в секции кода (на расстоянии менее 1 кБ) - это проверенный факт (см.здесь)
Hunter > "Если все отдельно, то сумма тактов первого столбца подсчитана немного некорректно ?" У bogrus'a в столбце фактически приведены номера тактов начала каждой инструкции. Если все imul зависимы, то каждая последующая ждет завершения предыдущей и общая задержка до готовности последнего результата будет равна = (числу imul)*4 = 4*4 = 16. Если же imul независимы, то каждая последующая может начинаться на следующем такте не дожидаясь завершения предыдущей, т.е. в итоге последний результат будет готов через 1+1+1+4 = 7 тактов. А вот нужно или нет учитывать задержку получения последнего результата, зависит от того какие инструкции идут дальше (должны они ждать или нет). Отсюда и "проблема" - чем ограничить рассматриваемый кусок кода и что считать задержкой. Поэтому все эти "точные" подсчеты обычно имеют смысл только для циклов.
leo Если мы рассматриваем чисто те четыре инструкции, то видим, что первая инструкция независима и, значит, выполнится за один такт. Зависимы остальные три инструкции. Я хотел заострить внимание вот на чем: в таблице Intel'а приведены два столбца: Latency и Throughput. Т.е., если исходить из написанного выше, то получается, что если инструкция независима, то мы берем значение из столбца throughput, а если зависима, то из latency. Вопрос был в том, содержит ли значение, указанное в latency не только время ожидания, но и время исполнения этой инструкции, или же нужно суммировать поля throughput и latency, т.е. ожидание+время выполнения ? А насчет записи в код в Virtual Mode, я не спорю Я просто говорил про чистый защищенный режим.
Hunter Дословный перевод IA-32 Optim. Latency - число тактов, необходимое исполнительному ядру для завершения всех микроопераций, из которых состоит IA-32 инструкция. Throughput - число тактов, которые необходимо выждать прежде чем в(ы)пускающие порты освободятся для принятия такой же инструкции снова. Для многих IA-32 инструкций throughput может быть значительно меньше, чем latency. Поэтому задержка результата операции определяется только latency. А вот следующая такая же инструкция, если она не зависима от предыдущей, может начать выполняться через время T = throughput после начала предыдущей. Вот диаграмма для наглядности: Код (Text): 0 1 2 3 4 5 6 7 8 imul ecx,ecx,5 |__|__|__|__| imul ecx,ecx,5 w w w |__|__|__|__| ;ждет пока будет готов ecx imul edx,ecx,5 |__|__|__|__| imul edx,ecx,5 |__|__|__|__| ;не ждет, т.к. ecx не изменялся |__|<----------- throughput = 1 = задержка поступления следующей такой же инструкции |___________|<-- latency = 4 = задержка готовности результата
leo, большое тебе спасибо за информацию и за указание на нужный мануал. Я почитал IA-32 Optim. и возник еще один вопрос. В определении "Throughput - The number of clock cycles required to wait before the issue ports are free to accept the same instruction again." интерес вызывает фраза "before the issue ports are free to accept the same instruction again". Т.е. время, которое потребуется для принятия такой же инструкции. Словно процессор "привыкает" к выполняемым инструкциям ). А что если далее будет следовать инструкция, отличная от текущей ? Сколько тогда потребуется тактов ?
Для P6 throughput стоит у операций умножения, деления, переходов, соответсвенно в портах исполнения есть отдельные блоки умножения, деления, выполнения переходов. Например если у div r32 throughput - 37, то это время будет занят только блок деления, но другие блоки этого порта смогут выполнять свои микрооперации
bogrus, ок, с P6 все понятно. А тогда как насчет Pentium 4 ? Там throughput определен для большого количества инструкций.
Hunter > "Словно процессор "привыкает" к выполняемым инструкциям ). А что если далее будет следовать инструкция, отличная от текущей ? Сколько тогда потребуется тактов ?" Говорить о "привыкании" можно только в отношении предсказания переходов и прогноза префетча кода и данных. Что касается исполнения мопов с изменением порядка, то здесь речь идет о планировании - как наилучшим образом без конфликтов передавать мопы по 4 портам на 8 исполнительных блоков и далее на 18 подблоков (см.структуру у А.Фога или IA-32 Optim. раздел Execution Units and Issue Ports). Рассылкой мопов занимаются планировщики (sheduler), входящие в состав реордер-буфера. Они как раз и учитывают latency и throughput инструкциий, чтобы посылать мопы на исполнение когда для них будут (предположительно) готовы операнды и освободятся исполнительные блоки. "Точный" анализ времени выполнения последовательности инструкций - дело непростое и неблагодарное. Кроме latency\througput и готовности операндов и флагов (зависимость от пред.инструкций) нужно еще как минимум учитывать конфликты портов и исполнительных блоков (поэтому они и приведены у Фога и частично в IA-32 Optim). "Что если далее будет следовать инструкция, отличная от текущей ?" Во-первых, речь нужно вести не об инструкциях, а о микрооперациях. Например, add r,m состоит из двух микроопераций (загрузка+сложение), а add m,r из трех (загрузка+сложение+сохранение). Поэтому, если регистровый операнд r еще не готов, а блок load свободен, то выполнение инструкции может "размазаться" во-времени: операнд из памяти загрузится в темп-регистр, а моп add будет ждать готовности r (возможно поэтому в IA-32 вообще не приводят латентности операций с памятью, а к цифрам Фога нужно подходить с умом). Во-вторых, исполнительный блок\подблок. К примеру, на P4 и целочисленные и вещественные умножения\деления (включая "скрытые", типа ААМ,AAD) выполняюются на fp_mul и fp_div, а все сдвиги в Northwood на MMX_shift. Поэтому операции, исполняемые на одном блоке, являются "зависимыми" по throughput. Но зависимость эта хитрая, т.к. целочисленные операции включают в себя доп. такты\мопы на конвертацию\пересылку операндов, поэтому каковы будут задержки между началом скажем fmul и mul и наоборот, я сказать не берусь. В-третьих, порты. Если операции выполняются на разных блоках, но в одном кластере (т.е.поступают через один порт), то соответственно это нужно учитывать. Например, mul и shr выполняются на разных блоках, но поступают через один порт (port1), поэтому вторая операция может начаться только через такт после первой. Хитрее обстоит дело с сочетанием быстрых (double speed) и обычных мопов. Во-первых, быстрые мопы типа add\sub могут выполняться и через порт 0 и через порт 1 (как там решит планировщик неизвестно). Во-вторых, задержка в одном порту зависит от последовательности, например fld+and принимаются в порт 0 за один такт, но and+fld могут быть приняты только в разных тактах (а вот всегда ли планировщик может изменить порядок точно не известно). Ну ес-но, если мопы вообще независимы по портам и блокам (например load с готовым адресом и любые вычисления), то они могут поступить на исполнение одновременно в одном такте. В-четвертых, возможны задержки в 0.5-1 такта на пересылку операндов в зависимых операциях, исполняемых в разных блоках (additional latency у Фога, и ничего в IA-32). Но самое хитрое и сложное - это работа планировщика - буферирование мопов из трэйс-кэша и изменение порядка их исполнения. Ситуация несколько упрощается, когда все операции однородные (быстрые\медленные, зависимые\независимые). В общем же случае за счет изменения порядка мопов может получиться такая каша, что кроме общих оценок "на вскидку" ничего сказать нельзя. Тебе кажется логичным один порядок исполнения, а у планировщика м.б. свои соображения (он же все-таки "железо-бетонный"). Что в этом случае означает "далее следует инструкция" - если эта инструкция могла быть уже выполнена на несколько тактов раньше ?
Hunter Сравни свою логику с "логикой" P4 модель 15.2.7 (F27h) Northwood. Три варианта циклов с двумя ADC, зависимыми по флагам (latency: 8 по IA-32 и 6 по Фогу; throuphut: 3 по IA-32 и 2 по Фогу) Код (Text): Вариант1 Вариант2 Вариант3 ----------- -------------- ----------- mov ecx,1000 mov ecx,1000 mov ecx,1000 @@: @@: @@: adc eax,ecx adc eax,ecx adc eax,ecx adc edx,ecx adc edx,ecx cmp ecx,1 dec ecx sub ecx,1 adc edx,ecx jnz @B jnz @B dec ecx jnz @B Реальное число тактов на цикл по wintest.exe by [b]bogrus[/b]: [b]14[/b] [b]4[/b](!!!) ~[b]5[/b] (!!?) Первые два варианта как-то объяснить можно (хотя второй только предположительно), а 3-й ИМХО не вписывается в логику первых двух.
leo, спасибо за ответ ! Еще одна мысль про оптимизацию. Дана последовательность: imul ecx,edx,5 imul ecx,ecx,5 imul eax,edx,5 Вторая инструкция зависима от первой по данным. Но т.к. процессор выполняет инструкции динамически, т.е. способен выполнить инструкции "вперед", если нет зависимости по данным, вполне логично предположить, что эти команды будут выполняться в такой последовательности: imul ecx,edx,5 imul eax,edx,5 imul ecx,ecx,5 Вопрос такой, имеет ли смысл переупорядочивать инструкции с целью оптимизации, если это сделает сам процессор ? (Вернее он сделает так, как посчитает нужным ) P.S. где лежит wintest.exe ? я протестирую на нескольких машинах, результаты выложу здесь.
Hunter Микрооперации (не команды!) планировщик переупорядочивает сам, вернее порты исполнения выбирают свои мопы (из буфера на 20 мопов), как только они станут готовы, переупорядочивать больше имеет смысл для устранения задержек декодирования, и не только ... Ты определись с камнем, т.к. то, что справедливо для семейства P6 не подойдет ко всем остальным leo В PIII ничуть не понятней Код (Text): Вариант1 Вариант2 Вариант3 ----------- -------------- ----------- 8 3-4 8 137443588__wintest.asm
bogrus, я знаю, что после декодирования используются микрооперации, а не команды, просто некорректно выразился, извиняюсь... А сейчас результаты нескольких интересных тестов представленных выше кусков кода. 1) Pentium 4 Celeron, ядро Northwood, CPUID=15.2.9 Результат для трех вариантов: 14, 4, 5 (как у leo) 2) Pentium 3 Celeron, ядро Coppermine, CPUID=6.8.10 Результат для трех вариантов: 8, 4, 8-9 (почти как у bogrus'а) 3) Pentium M Celeron, ядро Banias, CPUID=6.9.5 Результат для трех вариантов: 8, 3, 8-9 И, наконец, самое интересное: 4) Pentium 4 Celeron, ядро Willamette, CPUID=15.1.3 Результат для трех вариантов: 14, 14, 14 (!!!) Неожиданно, правда ? Я когда увидел - не поверил, перепроверил несколько раз, результат один и тот же. Интересно, что в интеловских мануалах, есть одна фраза: can vary between model encoding value = 3 and model < 2. Модель с номером 3 - это ядро Prescott, номер 2 - Northwood, 1 - Willamette. Т.е. по идее не должно быть отличий в значениях throughput и latency для ядер Northwood и Willamette. Это можно увидеть, посмотрев таблицу значений throughput и latency. Там дана информация лишь для моделей с номером 3 и 2 (т.е. номер 2 как бы обобщает два ядра), а также для мобильных процессоров. Также интересно то, что значения throughput и latency для команд dec и sub почти не отличаются. Это видно в ядре Willamette, но почему же тогда в P3 и в ядре Northwood есть такая большая разница ? Также вызывает вопросы то, что в таблице, для некоторых инструкций даны значения либо только для моделей с номером 3, либо с номером 2. А некоторых инструкций вообще нет в списке. Интересно, почему ? Вот и стоит задуматься об низкоуровневой оптимизации, если даже на разных ядрах результат может отличаться в 3-4 раза.
Hunter > "Вот и стоит задуматься об низкоуровневой оптимизации, если даже на разных ядрах результат может отличаться..." Дык, о чем и речь. Причем есть подозрение, что некоторые вещи могут отличаться и в разных степпингах одной модели (ядра). Вроде бы с той же ADC у меня получались разные результаты на CPUID 15.2.4 и 15.2.7 Отсюда видимо и ответ на вопрос "интеречно, почему ?". Потому, что ребята из Intel в творческом поиске - то одно изменят, то другое.
Интересно, такие неожиданные эффекты проявляются только при использовании инструкции adc, или нет ? leo, как ты отыскал такие комбинации ? Случайно ? Знаешь еще какие-нибудь интересные инструкции, ведущие себя "странно" ? Если знаешь, пиши, протестируем на разных машинах )
К вопросу о "неожиданных эффектах" и "странном" поведении. Во-первых, если внимательно почитатать примечание 1 к таблице латентностей в IA-32 Optimization, то увидим что латентности для комплексных инструкций (> 4 моп) - это цифры, основанные на консервативных оценках для наихудших условий, и что за счет изменения порядка исполнения действительное значение может быть в диапазоне от "несколько лучше" до "значительно лучше", чем номинальное значение, приведенное в таблице. Поэтому по табличным данным мы можем только утверждать, что на P4 латентность двух последовательных ADC в сумме не будет превышать 8+8=16 тактов, но при определенных условиях может быть significantly faster, что мы и видим в приведенных примерах. Так что с точки зрения IA-32 ничего странного в этом нет. Во-вторых, приведенные результаты для P4 Northwood можно объяснить, если для ADC throughput составляет 2 такта и latency 6-7. Следует также учесть разницу между dec ecx и sub ecx,1. Sub ни отчего не зависит и переустанавливает все флаги, а dec по сути состоит из двух мопов: sub ecx,1 и мопа коррекции флагов, т.к. dec не должна изменять флаг CF. В первом варианте тормозом является именно dec, т.к. она не изменяет флаг CF и в начале нового цикла CF определяется в результате предыдущей операции adc edx,ecx. В результате имеем непрерывную цепочку зависимых adc и следовательно задержка на цикл примерно равна сумме латентностей двух adc. Во втором варианте вместо dec стоит sub, и adc eax уже не зависит от adc edx в предыдущем цикле. В результате спекулятивного исполнения и изменения порядка получаем примерно такой поток мопов по тактам (при латентности adc = 7-8): 0)adc eax, 1)sub ecx, 2)adc eax, 3) sub ecx, 4) adc eax, 5) sub ecx, 6)adc eax, 7) sub ecx или ожидание, 8) наконец-то готовы данные для первого adc edx и с 8-го по 14-й такты выполняются 4 задержанных adc edx. Далее с 16 такта все повторяется и в итоге имеем 4 цикла за 16 тактов, т.е. 4 тика на цикл. (Почему первый adc edx,ecx может выполниться после нескольких измений регистра ecx думаю понятно - переименование регистров рулит - каждое новое значение ecx записывается в новый темп-регистр). Кстати и при латентности adc = 6 получается тоже 4 тика на цикл, но уже как 12 тиков на 3 цикла. В третьем варианте имеем примерно такую же картину, как и во втором. Разница в том, что теперь не adc edx зависит от adc eax, а наоборот, т.к. инструкция cmp делает adc edx независимой. Но на первый взгляд сама cmp зависит от dec, а dec зависит от adc - поэтому вроде бы задержка не может быть меньше латентности adc. Это только на первый взгляд, т.к. dec - это два мопа: первый моп sub ecx,1 ни отчего не зависит и может выполняться одновременно с adc и только моп коррекции флагов должен ждать ее завершения. Но для cmp важен именно первый моп, а флаги ей по барабану. Поэтому в результате имеем примерно такую картину как и в варианте 2, плюс 0.5-1 такта "на непредвиденные расходы". Как видим, подвести "теретическую базу" под известные результаты - можно. Но вот наоборот, оценить время выполнения с точностью 1-2 тика лишь по табличным данным часто бывает очень не просто.
leo, Как видим, подвести "теретическую базу" под известные результаты - можно А как тогда подвести под теоретическую базу совершенно различные результаты, опубликовынные выше, для разных ядер P4 ?
Hunter Другими словами, почему ядро Willamette отказывается выполнять ADC с перекрытием ? Возможные причины: 1) Возможно для этого ядра для ADC reciprocal throughput = latency в соответствии с данными А.Фога - не от балды же он их взял (если учесть год издания, время на исследование и написание, то вероятно большая часть его данных может относиться именно к Willamette, а не Northwood). Здесь следует иметь в виду, что определения throughput у Intel и reciprocal throughput у Фога несколько различаются: у Intel - это время освобождения портов для принятия следующей такой-же инструкции, а у Фога - это время через которое может начаться выполнение следующей независимой инструкции на том же исполнительном подблоке. Как видим у Intel более расплывчатое определение и упрекнуть их в неверных данных трудно. 2) Готовность данных и флагов. Как видно из результатов на ядре Northwood две независимые по флагам adc eax,ecx могут выполняться с задержкой в 2 такта. Это говорит о том, что результат операции в eax готов уже через 1.5-2 такта и остальное время тратится на коррекцию флагов (нужно сделать OR флагов CF и OF от двух сложений). Возможно, что в ядре Willamette использована более консервативная схема и результат операции помечается как готовый вместе с установкой флагов (через время = latency), поэтому две adc eax,ecx не могут перекрываться из-за "неготовности" eax. 3) Тупой планировщик, т.е. и throughput = 2-3 и результат в eax готов, но планировщик не выпускает следующую adc раньше 6-7 тактов после предыдущей. Хотя отличить этот вариант от первого на практике невозможно. Чтобы прояснить ситуацию нужно проверить результат выполнения на Willamete двух полностью независимых adc, например так: Код (Text): mov ecx,1000 @@: adc eax,ecx cmp ecx,1 ;можно add ebx,1 и т.п. adc edx,ecx sub ecx,1 jnz @B Если результат останется ~14, значит прав А.Фог и на этом ядре throughput = latency и две adc в принципе не могут перекрываться (возможно из-за бага в планировщике). Если будет меньше, то видимо верна гипотеза 2, т.е. throughput < latency, но только для полностью независимых инструкций.
leo, проверил на Willamette, результат равен 14. Т.е. получается, что информация доктора Агнера Фога касается именно этого ядра, и для других ядер может отличаться. Интересно, почему тогда Intel в своих мануалах обобщили данные для Willamette и Northwood ?