Анимация
JavaScript
|
Главная Библионтека AGAIN: MOV ЕАХ, [ESI; MOV EBX, [ESI + MOV ECX, [ESI + DEC EDX JNZ AGAIN 13*4096 + 20*4096 + У всех трех адресов, использованных здесь, совпадают set-значения, потому что разница между обрезанными адресами будет кратна 4096. Этот цикл будет очень плохо выполняться на Р1 и РРго. Во время записи в ЕСХ нет ни одного свободного кэш-ряда, поэтому тот. который был использован для значения, помещенного в ЕАХ, заполняется данными с [ESI+20*4096] до [ESI+20*4096+31] и записывает значение в ЕСХ. Затем во время чтения ЕАХ оказывается, что кэш-ряд, содержавший значение для ЕАХ, уже изменен, поэтому то же самое происходит с кэш-рядом, содержащим значение для ЕВХ, и т. д. Это пример нерационального использования кэша: цикл занимает около 60 тактов. Если третью линию изменить на: MOV ЕСХ, [ESI + 20*4096 + 32], МЫ пересечем фаницу в 32 байта и у нас будет другое set-значение, а следовательно, не возникнет никаких проблем с ассоциированием кэш-рядов к этим фем адресам. Цикл теперь занимает 3 такта (не считая первого раза) - весьма значительное улучшение! Стоит упомянуть, что у РММХ, Р2 и РЗ для каждого set-значения есть четыре кэш-ряда. (Некоторые документы Intel ошибочно утверждают, что у Р2 по два кэш-ряда на каждое set-значение). Определить, одинаковые ли у переменных set-значения, может оказаться достаточно сложным, особенно, если они разбросаны по различным сегментам. Лучшее, что можно придумать для избежания проблем подобного рода, - это держать все данные, используемые в критической части программы, внуфи одного блока, который будет не больше, чем кэш, либо в двух блоках, не больших, чем половина от его размера (например, один блок для статических данных, а другой для данных в стеке). Это будет гарантией того, что кэш-ряды используются оптимальным образом. Если критическая часть кода имеет дело с большими сфуктурами данных или данными, располагающимися в памяти случайным образом, стоит подумать о том, чтобы держать наиболее часто используемые переменные (счетчики, указатели, флаговые переменные и т. д.) в одном блоке с максимальным размером в 4 килобайта, чтобы использовались все кэш-ряды. Так как еще требуется стек для хранения параметров подпрограммы и возвращаемых адресов, лучшее, что можно сделать, - это скопировать иболее часто используемые статические данные в динамические переменные в стеке, после выхода из критической части кода скопировать обратно те из них, которые подверглись изменению. Чтение элемента данных, которого нет в кэше первого уровня, приводит к тому, что jecb кэш-ряд будет заполнен из кэша второго уровня. Если его нет и в кэше второго уровня, то результатом этого будет большая задержка, которая может оказаться совсем гигантской, в случае пересечения фаницы сфаниц. При считывании больших блоков данных из памяти скорость ограничивается временем, уходящим на заполнение кэш-рядов. Иногда возможно ее увеличение за счет изменения последовательности считывания данных: еще не считав данные из одного кэш-ряда, начать читать первый элемент из следующего кэш-ряда. Этот метод может повысить скорость на 20 - 40% при считывании данных из основной памяти, или кэша второго уровня на Р1 и РММХ, или из кэша второго уровня на РРго, Р2 и РЗ. Недостаток этого метода заключается в том, что код профаммы становится очень запутанным и сложным. Дополнительную информацию об этом методе можно получить с сайта www.intelligentfirm.com. Когда происходит запись по адресу, которого нет в кэше первого уровня, наР1 и РММХ значение будет отправлено прямо в кэш второго уровня или в память (в зависимости от того, как насфоен кэш второго уровня). Это займет примерно 100 ns. Если пишется восемь или более раз в тот же 32-байтный блок памяти, ничего не читая из него, и блок не находится в кэше первого уровня, возможно, стоит сделать фальшивое чтение из него, чтобы он был зафужен в кэш-ряд. Все последующие чтения будут производиться в кэш, что занимает всего лишь один такт. На Р1 и РММХ иногда происходит небольшая потеря в производительности при повторяющемся чтении по одному и тому же адресу без чтения из него между этим. На микропроцессорах поколений РРго, Р2 и РЗ запись обычно ведет к загрузке памяти в кэш-ряд, но возможно указать область памяти, с которой следует поступать иначе (см. "Pentium Pro Family Developers Manual, vol. 3: Operating System Writers Guide"). Другие пути увеличения скорости чтения и записи в память обсуждаются в разд. 6.27.8. У Р1 и РРго есть два буфера записи, у РММХ, Р2 и РЗ - четыре. На РММХ, Р2 и РЗ может быть до четырех незаконченных записей в некэшированную память без задержки последующих инсфукций. Каждый буфер записи может обрабатывать операнды до 64 бит длиной. Временные данные удобно хранить в стеке, потому что стековая область, как правило, находится в кэше. Тем не менее, следует избегать проблем с выравниваем, если элементы данных больше, чем размер слова стека. Если время жизни двух Сфуктур данных не пересекается, они могут разделять одну и ту же область памяти, чтобы повысить эффективность кэша. Это согласуется с общей практикой держать временные переменные в стеке. можность определить, совпадают ли эти биты у двух адресов следующим образом; дует удалить нижние пять битов каждого адреса, чтобы получить значение, кратное З2 Если разница между этими обрезанными значениями кратна 4096 (lOOOh), значит, у э. адресов одно и то же set-значение. Проиллюстрируем вышеизложенное с помощью следующего кода, где ESI содержр,. адрес, кратный 32. Хранение временных данных в регистрах, разумеется, более эффективно. Вследствие того что регистров в микропроцессорах семейства Pentium мало, возможно, потребуется использование [ESP] вместо [ЕВР] для адресации данных в стеке, чтобы освободить ЕВр для других целей. Однако не следует забывать, что значение ESP меняется каждый раз, когда выполняются команды PUSH или POP, к тому же нельзя использовать ESP в 16-битном режиме Windows, потому что прерывания таймера будут менять верхнее слово [ESP] стека совершенно непредсказуемо. В процессорах семейства Pentium существует кэш кода, который по назначению схож с кэшем данных. Размер кэша кода равен 8 килобайтам на Р1 и РРго и 16 килобайтам на РММХ, Р2 и РЗ. Важно, чтобы критические части кода (внутренние циклы) полностью умещались в кэш кода. Часто используемые процедуры следует помещать рядом друг с другом. Редко используемые ветви или процедуры нужно держать в "самом низу" раздела исполняемого кода. 6.8. Исполнение кода "в первый раз" Обычно исполнение кода в первый раз занимает намного больше времени, чем при последующих повторениях в силу следующих причин: зафузка кода из RAM в кэш занимает намного больше времени, чем его выполнение; любые данные, к которым обращается код, должны быть зафужены в кэш, что также занимает много времени, в случае повторного выполнения данные, как правило, уже находятся в кэше; любая инсфукция передачи управления не находится в буфере предсказывания переходов в первый раз, поэтому маловероятно, что она будет предсказана правильно (см. разд. 6.22); на Р1 декодирование инструкций является узким местом. Если определение длргаы инсфукции занимает один такт, то процессор не может раскодировать за такт две инструкции, так как он не знает, где начинается вторая инсфук-ция. Р1 решает эту проблему, запоминая длину любой инструкции, которая осталась в кэше. Как следствие, ряд инсфукций не спариваются на Р1 во время первого исполнения, если только первые две инструкции не занимают по одному байту. У РММХ, РРгс. Р2 и РЗ таких потерь нет. В силу этих четырех причин код внутри цикла будет занимать больше времени на исполнение в первый раз, нежели в последующие. Если имеется большой цикл, который не помещается в кэш кода, это может стать еле! ствием постоянных потерь производительности, потому что код будет исполняться, н используя преимуществ кэша. Следует реорганизовать цикл так, чтобы он умещался в кэш Если в цикле очень много переходов и вызовов, результат - потери из-за регулярны ошибок предсказания переходов. Как уже отмечалось, дополнительной потерей производительности станет периодиче ское обращение к сфуктурам данных, которые слишком велики для кэша данных. 6.9. Задержка генерации адр Чтобы вычислить адрес в памяти, который нужен инсфукции, фебуется один такт. Обыч но это осуществляется одновременно с выполнением предыдущей инсфукции или спаренньг инсфукций. Но если адрес зависит от результата инсфукции, которая выполнялась в преды душем такте, приходится ждать дополнительный такт, чтобы получить фебуемый адрес. Эт называется задержкой генерации адреса AGI (Address generation interlock). ADD EBX,4 / MOV EAX, [EBX] задержка AGI Задержки в данном примере можно избежать, оместив какие-нибудь другие инсфу* ции между ADD ЕВХ, 4 и MOV ЕАХ, [ЕВХ] или переписав код следующим образом: MOV ЕАХ,[ЕВХ+4] / ADD ЕВХ,4 Также можно получить задержку AGI при использовании инсфукций, которые ис пользуют ESP для адресации, например, PUSH, POP, CALL и RET, если ESP был имене в предыдущем такте такими инсфукциями, как MOV, ADD и SUB. У PI и РММХ ест специальная схема, чтобы предсказывать значение ESP после стековой операции, поэте му в данном случае не будет задержки AGI при изменении ESP с помощью PUSH, РО или CALL. При использовании RET это может случиться, только если у него есть числе вой операнд, который прибавляется к ESP. ADD ESP,4 / POP ESI POP EAX / POP ESI MOV ESP,EBP / RET CALL LI / LI: MOV EAX, [ES? + i RET / POP EAX RET 8 / POP EAX задержка AGI нет задержки, задержка AGI нет задержки нет задержки задержка AGI спариваются Инсфукция LEA также может стать причиной задержки AGI, если она использу« базовый или индексный регисф, который был изменен в предыдущем такте: INC ESI / LEA ЕАХ, [EBX + 4*ESI] ; задержка AGI У РРго, Р2 и РЗ нет задержки AGI при чтении из памяти и LEA, но есть задержка пр записи в память. Это не очень важно, если только последующий код не должен ждат пока операция записи не будет выполнена. 6.10. Спаривание целочисленных инструкций (PlnPMMX) 6.10.1. Совершенное спаривание у Р1 и РММХ есть два конвейера, выполняющих инструкции, которые соответственно называются U-конвейер и V-конвейер. В определенных условиях можно выполнить две инструкции одновременно, одну в U-конвейере, а другую в V-конвейере. Это практически удваивает скорость. 6.10.1.1. Имеет смысл перегруппировать инструкции для их выполнения условий спаривания. Следующие инструкции могут выполняться на любом конвейере: MOV регистр или память, непосредственный числовой операнд; PUSH регистр или число, POP регистр LEA, NOP; INC, DEC, ADD, SUB, CMP, AND, OR, XOR и некоторые разновидности TEST (см. разд. 6.26.14). Следующие инструкции спариваемы с другими в случае выполнения только на U-конвейере: ADC, SBB SHR, SAR, SHL, SAL с числом в качестве аргумента; ROR, ROL, RCR, RCL с единицей в качестве аргумента. Следующие инструкции спариваемы только в V-конвейере: м ближний вызовой; Ш короткий и ближний переход; Ш короткий и ближний условный переход. Все другие целочисленные инструкции могут выполняться только в U-конвейере и не могут спариваться. 6.10.1.2. Две следующие друг за другом инструкции будут спариваться, если соблю-деные следующие условия: первая инструкция спариваема в U-конвейере, а вторая - в V-конвейере; вторая инструкция не читает и не пишет из регистра, если пишет первая инструкция. Примеры MOV ЕАХ, ЕВХ / MOV ЕСХ, ЕАХ MOV ЕАХ, 1 / MOV ЕАХ, 2 MOV ЕВХ, ЕАХ / MOV ЕАХ, 2 ; чтение после записи, не спаривается ; запись после записи, не спаривается ; запись после чтения, спаривается MOV ЕВХ, ЕАХ / MOV ЕСХ, ЕАХ ; чтение после чтения, спаривается MOV ЕВХ, ЕАХ / INC ЕАХ ; чтение и запись после чтения, спаривается 6.10.1.3. Неполные регистры считаются полными регистрами. MOV AL, BL / MOV АН, О пишут в разные части одного и того же регистра, не спаривается. 6.10.1.4. Две инструкции, пишущие в разные части регистра флагов, могут спариваться, несмотря на правила 6.10.1.2 и 6.10.1.3. SHR ЕАХ, 4 / INC ЕВХ ; спаривается 6.10.1.5. Инструкция, которая пишет в регистр флагов, может спариваться с условным переходом, несмотря на правило 2. СМР ЕАХ, 2 / JA LabelBigger ; спаривается 6.10.1.6. Следующая комбинация инструкций может спариваться, несмотря на тот факт, что обе изменяют указатель на стек: POSH + POSH, POSH + CALL, POP + POP 6.10.1.7. Есть ограничения на спаривание инструкций с префиксами. Есть несколько типов префиксов: у инструкций, обращающихся к сегменту, не являющемуся сегментом по умолчанию, есть сегментный префикс; у инструкций, использующих 16-битные данные в 32-ом режиме или 32-битные данные в 16-битном режиме, есть префикс размера операнда; у инструкций, использующих 32-битную адресацию через регистры в 16-ти битном режиме, есть префикс размера адреса; у повторяющихся есть префикс повторения; у закрытых инструкций есть префикс LOCK; у многих инструкций, которых не было на процессоре 8086, есть двухбайтный опкод, чей первый байт равен OFH. Байт OFH считается префиксом только на Р1. Наиболее часто используемые инструкции с этим префиксом следующие: MOVZX, MOVSX, PUSH FS, POP FS, PUSH GS, POP GS, LPS, LGS, LSS, SETcc, ВТ, BTC, BTR, BTS, BSF, BSR, SHLD, SHRD и IMUL с двумя операндами и без числового операнда. На Р1 инструкция с префиксом может выполняться только в U-конвейере, кроме ближних условных переходов. На РММХ инструкции с префиксами размера операнда, размера адреса или OFH могут выполняться в любом конвейере, в то время как инструкции с префиксами сегмента. Повторения или LOCK могут выполняться только в U-конвейере. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 [ 59 ] 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |