Оптимизация для процессоров семейства Pentium (часть 3)

Дата публикации 25 фев 2017 | Редактировалось 1 авг 2017
Чтобы подсчитать количество прочитываемых регистров, вам нужно включить все регистры, которые считываются инструкцией. В это число входят все целочисленные регистры, флаговые регистры, указатель на стек, регистры плавающей запятой и регистры MMX. Регистр XMM идет за два, кроме тех случаев, когда используется только его часть, например в ADDSS и MOVHLPS. Сегментные регистр и указатель на инструкцию не учитываются. Например в 'SETZ AL' вы считаете флаговый регистр, но не AL. В 'ADD EBX, ECX' считаются и EBX и ECX, но не регистр флагов, потому что в него только производится запись. 'PUSH EAX' читает EAX и указатель на стек, а потом пишет в последний.
Инструкция FXCH является особым случаем. Она работает с помощью переименования, но не считывает никаких значений, поэтому она не попадает под действие какого-либо правила о задержке чтения из регистра. Инструкция FXCH ведет себя как один моп, который ни читает, ни пишет в регистр, когда дело касается правил задержек чтения из регистра.
Не путайте триплеты мопов с раскодировываемыми группами. Последняя может генерировать от одного до шести мопа, и даеж если раскодировываемая группа имеет три инструкции и генерирует три мопа, нет никакой гарантии, что эти три мопа попадут в RAT вместе.
Очередь между декодерами и RAT так котротка (10 мопов), что вы не можете предположить, что задержки чтения регистров не оказывают влияния на декодеры, или какие-то изменения в выводе декодеров не задерживают RAT.
Очень трудно предсказать, какие мопы пойдут через RAT вместе, если очередь не пуста, и для оптимизированного кода очередь должна быть пуста только после неправильно предсказанного перехода. Несколько мопов, генерируемых одной инструкцией не обязательно пойдут через RAT вместе; мопы просто берутся последовательно из очереди по три за раз. Последовательность не нарушается предсказанным переходом: мопы до и после перехода могут пойти через RAT вместе. Только неправильно предсказанный переход сбросит очередь и начнет все сначала, поэтому три следующих мопа точно попадут в RAT вместе.
Если три последовательных мопа читают больше, чем из двух регистров, то вы бы, конечно, предпочли, чтобы они не шли вместе друг с другом в RAT. Вероятность того, что они пойдут вместе - одна третья. Задержка чтения трех или четырех выгруженных регистра в одной тройке мопов - один такт. Вы можете считать задержку в один такт эквивалентом загрузки еще трех мопов в RAT. С вероятностью в 1/3 что три мопа пойдут в RAT вместе, средняя задержка будет эквивалентна 3/3 = 1 моп. Чтобы посчитать среднее время, которое займет для некоторого кода проход через RAT, добавьте количество потенциальных задержек чтения регистров к количеству мопов и поделите на три. Вы можете видеть, что нет смысла убирать задержки с помощью добавления дополнительных регистров, пока вы точно не уверены, какие мопы пойдут в RAT вместе, или же вы можете предотвратить больше, чем одну задержку чтения регистра с помощью одной дополнительной инструкции.
В ситуациях, когда вашей целью является производительность 3 мопа за такт, ограничение в чтении только двух постоянных регистров за такт может стать узким местом, которое будет трудно обойти. Возможные пути обойти возможные задержки чтения регистра следующие:
Держать мопы, которые читают один и тот же регистр близко друг к другу, чтобы они попали в один триплет.
Держать мопы, которые читают разные регистры подальше друг от друга, чтобы они не могли попасть в один триплет.
Располагать мопы, которые читают регистр не дальше трех-четырех триплетов от инструкции, которая писала или изменяла этот регистр, чтобы он не был выгружен прежде, чем будут произведены все необходимые операции чтения (не важно, если у вас есть переход, если он будет правильно предсказан). Если у вас есть основания полагать, что запись в регистр будет задержана, вы можете спокойно читать из него еще некоторое количество инструкций.
Использовать абсолютные адреса вместо указателей, чтобы снизить количество читаемых регистров.
Вы можете переименовать регистр в триплете, если это не вызывает задержку, чтобы предотвратить задержку чтения для этого регистра в одном или больше следующих далее триплетов. Пример: 'MOV ESP,ESP / ... / MOV EAX,[ESP+8]'. Этот метод стоит дополнительный моп, поэтому его стоит применять только, если среднее предполагаемое количество предотвращенных задержек чтения больше 1/3.
Для инструкций, которые генерируют больше одного мопа, вы можете захотить узнать порядок мопов, генерируемых инструкцией, чтобы сделать предсказание возможных задержек чтения регистра более точным. Ниже я перечислил наиболее общие случаи.
Запись в память
Запись в память генерирует два мопа. Первый (в порт 4 ) - это операция сохранения, считывающая регистр, который нужно сохранить, второй моп вычисляет адрес памяти, читая любые регистры-указатели.
Код (ASM):
  1. MOV [EDI], EAX
Первый моп читает EAX, второй читает EDI.
Код (ASM):
  1. FSTP QWORD PTR [EBX+8*ECX]
Первый моп читает ST(0), второй моп читает EBX и ECX.
Чтение и модифицирование
Инструкция, которая читает операнд в памяти и модифицирует регистр с помощью какой-либо арифметической или логической операции генерирует два мопа. Первый (порт 2) - это инструкция загрузки из памяти, читающая любой регистр-указатель, второй моп - это арифметическая инструкция (порт 1 или 2), читающая и пишущая в регистр назначения и, возможно, пишущая в регистр флагов.
Код (ASM):
  1.       ADD EAX, [ESI+20]
Первый моп читает ESI, второй моп читает EAX и пишет EAX и регистр флагов.
Чтение/модифицирование/запись
Инструкция этого рода генерирует четыре мопа. Первый моп (порт 2) читает регистр-указатель, второй (порт 0 или 1) читает и производит запись в исходный регистр и, возможно, пишет в регистр флагов, третий моп (порт 4) считывает только временный результат, которые не учитывается здесь, четвертый (порт 3) читает все регистры-указатели снова. Так как первый и четвертый регистр не могут идти в RAT вместе, вы не получить преимущество от того, что они читают один и тот же регистр-указатель.
Код (ASM):
  1.       OR [ESI+EDI], EAX
Первый моп читает ESI и EDI, второй читает EAX и пишет в EAX и в регистр флагов, третий читает только временный результат, четвертый читает ESI и EDI снова. Вне зависимости о того, в каком порядке эти мопы пойдут в RAT, вы можете быть уверены, что моп, который читает EAX пойдет вместе с тем, который читает ESI и EDI. Поэтому задержка чтения регистра неизбежна в этой инструкции, если только один из регистров не был изменен ранее.
Сохранение регистра в стек.
Сохранение регистра в стек генерирует 3 мопа. Первый (порт 4) - это инструкция сохранения, чтение регистра. Второй моп (порт 4) генерирует адрес, считывая указатель на стек. Третий (порт 0 или 1) вычитает размер слова из указателя на стек, читая и модифицируя его.
Обратная операция генерирует два мопа. Первый (порт 2) загружает значение, считывая указатель на стек и записывая значение в регистр. Второй моп (порт 0 или 1) корректирует указатель на стек, читая и модифицируя его.
Вызов (CALL)
Ближний вызов генерирует 4 мопа (порты 1, 4, 3, 01). Первый два мопа читают указатель на инструкцию (EIP), который не учитывается, потому что он не может быть переименован. Третий моп читает указатель на стек. Последний моп читает и модифицирует его.
Возврат (RET)
Ближний возврат генерирует 4 мопа (порт 2, 01, 01, 1). Первый моп считывает ESP. Третий читает и модифицирует его.
Пример того, как избежать задержку чтения регистра можно найти в примере 2.6.
17. Выполнение кода не по порядку (PPro, PII и PIII)
Буфер перегруппировки вмещает 40 мопов. Каждый моп ждет в ROB, пока все операнды не будут готовы и не появится свободный модуль для их выполнения. Это делает возможным выполнение кода не по порядку. Если одна часть кода задерживается из-за загрузки в кэш, это не повлияет на выполнение других частей кода, если они не зависят от первой части.
Запись в память не может быть выполнена по порядку, если она зависит от других записей. Есть четыре буфера записи, поэтому вы можете ожидать большие потери производительности во время такой записи в память или записи в некэшированную память, поэтому рекомендуется, чтобы вы делали четыре записи за раз и были уверены в том, что у процессора есть чем еще заняться, прежде чем вы нагрузите его еще четырьмя записями. Чтение из памяти и другие инструкции могут выполняться не по порядку, кроме IN, OUT и синхронизирующих операций.
Если ваш код пишет в память по какому-то адресу, а вскоре считывает оттуда же, тогда чтение по ошибке может быть произведено до записи, потому ROB не имеет понятия об адресах во время перегруппировки. Эта ошибка отлавливается, когда подсчитывается адрес памяти, поэтому операция чтения в таком случае выполняется снова. Потери при этом составляют 3 такта. Единственный путь избежать этого - это убедиться, что у модуля выполнения будет занят делом между последовательными записью в память и чтением из нее по тому же адресу.
Есть несколько модулей выполнения, сгруппированных вокруг пяти портов. Порт 0 и 1 - для арифметических операций. Простые пересылки, арифметические и логические операции попадают в порт 0 или 1, в зависимости от того, какой из них свободен. Порт 0 также обрабатывает умножение, деление, целочисленные побитовые сдвиги и операции с плавающей запятой. Порт 1 в дополнение к основным функциям занимается переходами и некоторыми операциями MMX и XMM. Порт 2 обрабатывает все чтения из памяти и несколько строковых операций. В главе 29 вы найдете полный список мопов, генерируемых инструкциями кода с указанием того, в какой порт они попадают. Заметьте, что все операции записи в память требуют два мопа, один для порта 3, а другой для порта 4, в то время как операции чтения из памяти требуют только один моп (порт 2).
В большинстве случаев каждый порт может получать один моп за такт. Это означает, что вы можете выполнять до 5 операций мопов за такт, если они попадают в пять разных портов, но так как есть ограничение на 3 мопа за такт, установленное ранее в конвеере, вам никогда не удастся выполнить больше 3 мопов за такт в среднем.
Вы должны убедиться, что никакой порт не получает больше одной трети мопов, если вы хотите достичь производительности в 3 мопа за такт. Используйте таблицу мопов в главе 29 и подсчитайте, сколько мопов попадет в каждый порт. Если порты 0 и 1 перегружены, в то время как порт 2 свободен, вы можете улучшить свой код, заменив некоторые пересылки вида "регистр, регистр" или "регистр, числовое значение" на пересылку вида "регистр, память", чтобы перенести часть нагрузки с портов 0 и 1 на порт 2.
Большинство мопов занимают один такт для выполнения, но умножения, деления и многие операции с плавающей запятой занимают больше:
Сложения и вычитания плавающей запятой занимают 3 такта, но модуль выполнения полностью конвейеризован, поэтому он может принимать новые FADD и FSUB каждый такт, пока предыдущие инструкции выполняются (конечно, если они независимы друг от друга).
Целочисленное умножение занимает 4 такта, умножения плавающей запятой - 5, а умножения MMX - 3 такта. Умножения целых чисел и MMX конвейеризованы, поэтому они могут получать новые инструкции каждый такт. Умножения плавающей запятой частично конвейеризованы: модуль выполнения может получать новую инструкцию FMUL через два такта после получения предыдущей, поэтому максимальная производительность будет равная одному FMUL за каждые два такта. "Дыры" между FMUL'ами нельзя заполнить целочисленными умножениями, так как они используют ту же схему. Сложения и умножения XMM занимают 3 и 4 такта соответственно и полностью конвйееризованы. Но так как каждый регистр XMM представлен в виде двух 64-х битных регистров, упакованная инструкция XMM будет состоять из двух мопов, а производительность будет равна одной арифметической инструкции XMM каждые два такта. Инструкции сложения и умножения XMM могут выполняться параллельно, потому что они не используют одни и те же порты.
Деление целых чисел и чисел с плавающей запятой занимает до 39 тактов и не конвейеризовано. Это означает, что модуль выполнения не может начать новое деление, пока предыдущие не будет выполнено. Вышесказанное относится к квадратному корню и трансцендентальным функциям.
Также инструкции переходов, вызовов и возвратов не полностью конвейеризованы. Вы не можете выполнить новый переход в первом же такте после предыдущего перехода. Поэтому максимальная производительность для переходов, вызовов и возвратов равна одному за каждые два такта.
Конечно вы должны избегать инструкций, которые генерируют много мопов. Например инструкцию LOOP XX нужно заменить на DEC ECX / JNZ XX.
Если у вас есть две последовательные POP-инструкции, вы можете заменить их на другие, чтобы снизить количество мопов.
Код (ASM):
  1.  POP ECX
  2.  POP EBX
  3.  POP EAX
можно заменить на:
Код (ASM):
  1.  MOV ECX,[ESP]
  2.  MOV EBX,[ESP+4]
  3.  MOV EAX,[ESP+8]
  4.  ADD ESP,12
Первый код генерирует 6 мопов, последующий - 4 и декодируется быстрее. Делать то же самое с инструкциями PUSH менее выгодно, потому разбитый код будет генерировать задержки чтения регистров, если только у вас нет других инструкций, которые можно вставить между ними или какие-то регистры не были переименованы раньше. Делать подобное с инструкциями CALL и RET означает мешать правильному предсказанию перехода. Также обратите внимание, что 'ADD ESP' может вызвать задержку AGI в более ранних моделях процессоров.
18. Вывод из обращения (PPro, PII и PIII)
Вывод из обращения (retirement) — это процесс, когда временные регистры, используемые мопами, копируют в постоянные регистры EAX, EBX и так далее. Когда моп выполнен, он помечается в ROB как готовый к выводу из обращения.
Станция вывода из обращения может обрабатывать три мопа за такт. Может показаться, что здесь нет никакой проблемы, потому что вывод уже ограничен в RAT тремя мопами за такт. Тем не менее, вывод из обращения может стать узким местом по двум причинам. Во-первых, инструкции должны выводиться из обращения по порядку. Если моп был выполнен не по порядку, то он не может быть выведен из обращения, пока все мопы, предшествующие ему по порядку, не будут выведены из обращения до него. И второе ограничение - это то, что полученные переходы должны быть выведены из обращения в трех первых слотах станции. Как декодеры D1 и D2 могут быть неактивны, если следующая инструкция помещается только в D0, последние два слота станции вывода из обращения могут быть неактивны, если следующий моп, который должен быть выведен из обращения — это полученный (taken) переход. Это важно, когда у вас есть маленький цикл, количество мопов в котором не кратно трем.
Все мопы находятся в буфере перегруппировки (ROB), пока они не будут изъяты из обращения. ROB вмещает 40 мопов. Это устанавливает ограничение на количество инструкций, которые могут быть выполнены во время большой задержки, вызванной, например, делением или другой медленной операцией. Прежде, чем будет закончено деление, ROB будет заполнен выполняющимися мопами, ожидающими своего изъятия из обращения. Только когда деление будет закончено и изъято, последующие могут сами начать изыматься из обращения, потому что этот процесс должен выполняться по порядку.
В случае предварительного выполнения предсказанных переходов (смотри главу 22), предварительно выполненные мопы не могут быть изъяты из обращения, пока процессор не убедится, что предсказание верно. Если окажется обратное, тогда предварительно выполненные мопы сбрасываются без изъятия из обращения.
Следующие инструкции не могут быть предварительно выполнены: записи в память, IN, OUT и синхронизирующие операции.
19. Частичные задержки (PPro, PII и PIII)
19.1 Частичные задержки регистра
Частичная задержка регистра - это проблема, которая возникает, когда вы пишите в часть 32-х битного регистра, а затем читаете из всего регистра или его большей части. Пример:
Код (ASM):
  1.        MOV AL, BYTE PTR [M8]
  2.         MOV EBX, EAX            ; частичная задержка регистра
Происходит задержка в 5-6 тактов. Причина состоит в том, что в соответствие AL был поставлен временный регистр (чтобы сделать его независимым от AH). Модулю выполнения пришлось ждать, пока запись в AL не будет выведена из обращения, прежде чем станет возможным соединить значение AL c тем, что находится в остальной части EAX. Задержку можно избежать, изменив код, поменяв код на:
Код (ASM):
  1.         MOVZX EBX, BYTE PTR [MEM8]
  2.         AND EAX, 0FFFFFF00h
  3.         OR EBX, EAX
Конечно вы можете избежать частичную задержку, поместив другие инструкции после записи, чтобы у последней было время на вывод из обращения до того, как вы начнете читать из полного регистра.
Вам нужно остерегаться частичных задержек, когда вы смешиваете различные размеры данных (8, 16 и 32):
Код (ASM):
  1.         MOV BH, 0
  2.         ADD BX, AX              ; задержка
  3.         INC EBX                 ; задержка
У вас не будет задержки при чтении части регистра после записи в целый регистр или его большую часть:
Код (ASM):
  1.         MOV EAX, [MEM32]
  2.         ADD BL, AL              ; нет задержки
  3.         ADD BH, AH              ; нет задержки
  4.         MOV CX, AX              ; нет задержки
  5.         MOV DX, BX              ; задержка
Самый легкий путь избегать частичные задержки регистра - это всегда использовать полные регистры и использовать MOVZX или MOVSX при чтении из операндов более мелкого размера. Эти инструкции быстры на PPro, PII и PIII, но медленны на более ранних процессорах. Если вы хотите, чтобы ваш код выполнялся достаточно быстро на всех процессорах, то существует разумный компромисс. 'MOVZX EAX,BYTE PTR [M8]'
Код (ASM):
  1.         XOR EAX, EAX
  2.         MOV AL, BYTE PTR [M8]
Процессоры PPro, PII и PIII делают специальное исключение для этой комбинации, поэтому при последующем чтении из EAX задержки не возникнет. Происходит это потому, что регистр помечается как пустой, когда он XOR'ится сам с собой. Процессор помнит, что старшие 24 бита равны нулю и за счет этого избегается задержка. Этот механизм работает только со следующими комбинациями:
Код (ASM):
  1.         XOR EAX, EAX
  2.         MOV AL, 3
  3.         MOV EBX, EAX            ; нет задержки
  4.         XOR AH, AH
  5.         MOV AL, 3
  6.         MOV BX, AX              ; нет задержки
  7.         XOR EAX, EAX
  8.         MOV AH, 3
  9.         MOV EBX, EAX            ; задержка
  10.         SUB EBX, EBX
  11.         MOV BL, DL
  12.         MOV ECX, EBX            ; нет задержки
  13.         MOV EBX, 0
  14.         MOV BL, DL
  15.         MOV ECX, EBX            ; задержка
  16.         MOV BL, DL
  17.         XOR EBX, EBX            ; нет задержки
Установка регистра в ноль вычитанием его из самого себя работает так же как XOR, но обнуление регистра с помощью инструкции MOV не предотвращает задержку.
Вы можете установите XOR снаружи цикла:
Код (ASM):
  1.         XOR EAX, EAX
  2.         MOV ECX, 100
  3. LL:     MOV AL, [ESI]
  4.         MOV [EDI], EAX          ; задержка
  5.         INC ESI
  6.         ADD EDI, 4
  7.         DEC ECX
  8.         JNZ LL
Процессор помнит, что старшие 24 бита EAX равны нулю пока не происходит вызов прерывания, неправильного предсказания перехода или другого синхронизирующего события.
Вы должны помнить, что необходимо нейтрализовывать возможные частичные задержки регистра вышеописанным способом при вызове процедуры, которая будет PUSH'ить полный регистр:
Код (ASM):
  1.         ADD BL, AL
  2.         MOV [MEM8], BL
  3.         XOR EBX, EBX            ; нейтрализируем BL
  4.         CALL _HighLevelFunction
Большинство языков высокого уровня PUSH'ат EBX в начале процедуры, что в вышеприведенном примере привело к частичной задержке регистра, если бы ее не нейтрализовали.
Обнуление регистра с помощью XOR не устраняет его зависимость от предыдущих инструкций:
Код (ASM):
  1.         DIV EBX
  2.         MOV [MEM], EAX
  3.         MOV EAX, 0              ; прерываем зависимость
  4.         XOR EAX, EAX            ; предотвращаем частичную задержку регистра
  5.         MOV AL, CL
  6.         ADD EBX, EAX
Обнуление регистра дважды может показаться излишней, но без 'MOV EAX, 0' последние инструкции будут ждать, пока выполниться медленный DIV, а без 'XOR EAX, EAX' случиться частичная задержка регистра.
Инструкция 'FNSTSW AX' особенная: в 32-х битном режиме она ведет себя так же, как если бы писала в весь EAX. Фактически она делает в 32-х битном режиме следующее:
AND EAX,0FFFF0000h / FNSTSW TEMP / OR EAX,TEMP
Поэтому при чтении регистра после этой инструкции у вас не возникнет частичной задержки регистра в 32-х битном режиме:
Код (ASM):
  1.     FNSTSW AX / MOV EBX,EAX         ; задержка только в 16-ти битном режиме
  2.     MOV AX,0  / FNSTSW AX           ; задержка только в 32-х битном режиме
19.2 Частичные задержки флагов
Регистр флагов также может вызвать частичную задержку:
Код (ASM):
  1.         CMP EAX, EBX
  2.         INC ECX
  3.         JBE XX          ; задержка
Инструкция JBE читает и флаг переноса, и флаг нуля. Так как инструкция INC изменяет флаг нуля, но не флаг переноса, то инструкции JBE приходится подождать, пока две предыдущие инструкции не будут выведены из обращения, прежде чем она сможет скомбинировать флаг переноса от инструкции CMP с флагом нуля от инструкции INC. Подобная ситуация больше похожа на баг, чем на преднамеренную комбинацию флагов. Чтобы скорректировать эту ситуацию, измените INC ECX на ADD ECX, 1. Похожий баг, вызывающий задержку, - это 'SAHF / JL XX'/. Инструкция JL тестирует флаг знака и флаг переполнения, но не меняет последний. Чтобы исправить это, измените 'JL XX' на 'JS XX'.
Неожиданно (и в противоположность тому, что говорят руководства от Intel), частичная задержка регистра может случиться, если были изменены какие-то биты регистра флагов, а затем считаны неизмененные.
Код (ASM):
  1.         CMP EAX, EBX
  2.         INC ECX
  3.         JC  XX          ; задержка
но не при чтении только измененных битов:
Код (ASM):
  1.         CMP EAX, EBX
  2.         INC ECX
  3.         JE  XX          ; нет задержки
Частичные задержки флагов возникают, как правило, при использовании инструкций, которые считывают много или все биты регистра флагов, например LAHF, PUSHF, PUSHFD. Инструкции, которые вызывают задержку, если за ними идут LAHF или PUSHF(D), следующие: INC, DEC, TEST, битовые тесты, битовые сканирования, CLC, STC, CMC, CLD, STD, CLI, STI, MUL, IMUL, и все виды битовых сдвигов и вращений. Следующие инструкции не вызывают задержки: AND, OR, XOR, ADD, ADC, SUB, SBB, CMP, NEG. Странно, что TEST и AND ведут себя различно, хотя по описанию они делают с флагами одно и то же. Вы можете использовать инструкции SETcc вместо LAHF или PUSHF(D) для сохранения значения флага, чтобы избежать задержки.
Код (ASM):
  1.     INC EAX   / PUSHFD      ; задержка
  2.     ADD EAX,1 / PUSHFD      ; нет задержки
  3.     SHR EAX,1 / PUSHFD      ; задержка
  4.     SHR EAX,1 / OR EAX,EAX / PUSHFD   ; нет задержки
  5.     TEST EBX,EBX / LAHF     ; задержка
  6.     AND  EBX,EBX / LAHF     ; нет задержки
  7.     TEST EBX,EBX / SETZ AL  ; нет задержки
  8.     CLC / SETZ AL           ; задержка
  9.     CLD / SETZ AL           ; нет задержки
Потери при частичной задержки флагов примерно равны 4 тактам.
19.3 Задержки флагов после сдвигов и вращений

При чтении любого флагового бита после сдвига или вращения (кроме сдвига и вращения на 1, т.н. короткая форма) может возникнуть задержка, похожая на частичную задержку флагов:
Код (ASM):
  1.     SHR EAX,1 / JZ XX                ; нет задержки
  2.     SHR EAX,2 / JZ XX                ; задержка
  3.     SHR EAX,2 / OR EAX,EAX / JZ XX   ; нет задержки
  4.     SHR EAX,5 / JC XX                ; задержка
  5.     SHR EAX,4 / SHR EAX,1 / JC XX    ; нет задержки
  6.     SHR EAX,CL / JZ XX               ; задержка, даже если CL = 1
  7.     SHRD EAX,EBX,1 / JZ XX           ; задержка
  8.     ROL EBX,8 / JC XX                ; задержка
Потери для этого вида задержек приблизительно равны 4 тактам.
19.4 Частичные задержки памяти

Частичная задержка памяти похожа на частичную задержку регистра. Она случается, когда вы смешиваете размеры данных применительно к одному адресу в памяти:
Код (ASM):
  1.         MOV BYTE PTR [ESI], AL
  2.         MOV EBX, DWORD PTR [ESI]        ; частичная задержка в памяти
Здесь случается задержка, потому что процессор должен скомбинировать байт, записанный из AL с тремя следующими байтами, которые были в памяти раньше, чтобы получить все четыре байта, необходимые для произведения записи в EBX. Потери приблизительно равны 7-8 тактов.
В отличии от частичных задержек регистра, частичная задержка памяти случается, когда вы записывает операнд в память, а затем читаете его часть, если она не начинается по тому же адресу:
Код (ASM):
  1.         MOV DWORD PTR [ESI], EAX
  2.         MOV BL, BYTE PTR [ESI]          ; нет задержки
  3.         MOV BH, BYTE PTR [ESI+1]        ; задержка
Вы можете избежать этой задержки, если измените последнюю лини на 'MOV BH, AH', но подобное решение невозможно в ситуации, подобной этой:
Код (ASM):
  1.         FISTP QWORD PTR [EDI]
  2.         MOV EAX, DWORD PTR [EDI]
  3.         MOV EDX, DWORD PTR [EDI+4]      ; задержка
Интересно, что частичная задержка памяти может также случиться при записи и чтении совершенно разных адресов, если у них одинаковое set-значение в разных банках кэша:
Код (ASM):
  1.         MOV BYTE PTR [ESI], AL
  2.         MOV EBX, DWORD PTR [ESI+4092]   ; нет задержки
  3.         MOV ECX, DWORD PTR [ESI+4096]   ; задержка

0 2.134
Mikl___

Mikl___
Супермодератор
Команда форума

Регистрация:
25 июн 2008
Публикаций:
14