Оптимизация для процессоров семейства Pentium: 19. Частичные задержки (PPro, PII и PIII)

Дата публикации 22 авг 2002

Оптимизация для процессоров семейства Pentium: 19. Частичные задержки (PPro, PII и PIII) — Архив WASM.RU

19.1 Частичные задержки регистра

Частичная задержка регистра - это проблема, которая возникает, когда вы пишите в часть 32-х битного регистра, а затем читаете из всего регистра или его большей части. Пример:

Код (Text):
  1.  
  2.         MOV AL, BYTE PTR [M8]
  3.         MOV EBX, EAX            ; частичная задержка регистра

Происходит задержка в 5-6 тактов. Причина состоит в том, что в соответствие AL был поставлен временный регистр (чтобы сделать его независимым от AH). Модулю выполнения пришлось ждать, пока запись в AL не будет выведена из обращения, прежде чем станет возможным соединить значение AL c тем, что находится в остальной части EAX. Задержку можно избежать, изменив код, поменяв код на:

Код (Text):
  1.  
  2.         MOVZX EBX, BYTE PTR [MEM8]
  3.         AND EAX, 0FFFFFF00h
  4.         OR EBX, EAX

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

Вам нужно остерегаться частичных задержек, когда вы смешиваете различные размеры данных (8, 16 и 32):

Код (Text):
  1.  
  2.         MOV BH, 0
  3.         ADD BX, AX              ; задержка
  4.         INC EBX                 ; задержка

У вас не будет задержки при чтении части регистра после записи в целый регистр или его большую часть:

Код (Text):
  1.  
  2.         MOV EAX, [MEM32]
  3.         ADD BL, AL              ; нет задержки
  4.         ADD BH, AH              ; нет задержки
  5.         MOV CX, AX              ; нет задержки
  6.         MOV DX, BX              ; задержка

Самый легкий путь избегать частиные задержки регистра - это всегда использовать полные регистры и использовать MOVZX или MOVSX при чтении из операндов более мелкого размера. Эти инструкции быстры на PPro, PII и PIII, но медленны на более ранних процессорах. Если вы хотите, чтобы ваш код выполнялся достаточно быстро на всех процессорах, то существует разумный компромисс. 'MOVZX EAX,BYTE PTR [M8]'

Код (Text):
  1.  
  2.         XOR EAX, EAX
  3.         MOV AL, BYTE PTR [M8]

Процессоры PPro, PII и PIII делают специальное исключение для этой комбинации, поэтому при последующем чтении из EAX задержки не возникнет. Происходит это потому, что регистр помечается как пустой, когда он XOR'ится сам с собой. Процессор помнит, что верхние 24 бита равны нулю и за счет этого избегается задержка. Этот механизм работает только со следующими комбинациями:

Код (Text):
  1.  
  2.         XOR EAX, EAX
  3.  
  4.         MOV AL, 3
  5.         MOV EBX, EAX            ; нет задержки
  6.  
  7.         XOR AH, AH
  8.         MOV AL, 3
  9.         MOV BX, AX              ; нет задержки
  10.  
  11.         XOR EAX, EAX
  12.         MOV AH, 3
  13.         MOV EBX, EAX            ; задержка
  14.  
  15.         SUB EBX, EBX
  16.         MOV BL, DL
  17.         MOV ECX, EBX            ; нет задержки
  18.  
  19.         MOV EBX, 0
  20.         MOV BL, DL
  21.         MOV ECX, EBX            ; задержка
  22.  
  23.         MOV BL, DL
  24.         XOR EBX, EBX            ; нет задержки

Установка регистра в ноль вычитанием его из самого себя работает так же как XOR, но обнуление регистра с помощью инструкции MOV не предотвращает задержку.

Вы можете установите XOR снаружи цикла:

Код (Text):
  1.  
  2.         XOR EAX, EAX
  3.         MOV ECX, 100
  4. LL:     MOV AL, [ESI]
  5.         MOV [EDI], EAX          ; задержка
  6.         INC ESI
  7.         ADD EDI, 4
  8.         DEC ECX
  9.         JNZ LL

Процессор помнит, что верхние 24 бита EAX равны нулю пока не происходит вызов прерывания, неправильного предсказания перехода или другого синхронизирующего события.

Вы должны помнить, что необходимо нейтрализовывать возможные частичные задержки регистра вышеописанным способом при вызове процедуры, которая будет PUSH'ить полный регистр:

Код (Text):
  1.  
  2.         ADD BL, AL
  3.         MOV [MEM8], BL
  4.         XOR EBX, EBX            ; нейтрализируем BL
  5.         CALL _HighLevelFunction

Большинство языков высокого уровня PUSH'ат EBX в начале процедуры, что в вышеприведенном примере привело к частичной задержке регистра, если бы ее не нейтрализовали.

Обнуление регистра с помощью XOR не устраняет его зависимость от предыдущих инструкций:

Код (Text):
  1.  
  2.         DIV EBX
  3.         MOV [MEM], EAX
  4. <p>
  5.         MOV EAX, 0              ; прерываем зависимость
  6.         XOR EAX, EAX            ; предотвращаем частичную задержку регистра
  7.         MOV AL, CL
  8.         ADD EBX, EAX

Обнуление регистра дважды может показаться излишней, но без 'MOV EAX, 0' последние инструкции будут ждать, пока выполниться медленный DIV, а без 'XOR EAX, EAX' случиться частичная задержка регистра.

Инструкция 'FNSTSW AX' особенная: в 32-х битном режиме она ведет себя так же, как если бы писала в весь EAX. Фактически она делает в 32-х битном режиме следующее:

Код (Text):
  1.  
  2. AND EAX,0FFFF0000h / FNSTSW TEMP / OR EAX,TEMP

Поэтому при чтении регистра после этой инструкции у вас не возникнет частичной задержки регистра в 32-х битном режиме:

Код (Text):
  1.  
  2.     FNSTSW AX / MOV EBX,EAX         ; задержка только в 16-ти битном режиме
  3.     MOV AX,0  / FNSTSW AX           ; задержка только в 32-х битном режиме

19.2 Частичные задержки флагов

Регистр флагов также может вызвать частичную задержку:

Код (Text):
  1.  
  2.         CMP EAX, EBX
  3.         INC ECX
  4.         JBE XX          ; задержка

Инструкция JBE читает и флаг переноса, и флаг нуля. Так как инструкция INC изменяет флаг нуля, но не флаг переноса, то инструкции JBE приходится подождать, пока две предыдущие инструкции не будут выведены из обращения, прежде чем она сможет скомбинировать флаг переноса от инструкции CMP с флагом нуля от инструкции INC. Подобная ситуация больше похожа на баг, чем на преднамеренную комбинацию флагов. Чтобы скорректировать эту ситуацию, измените INC ECX на ADD ECX, 1. Похожий баг, вызывающий задержку, - это 'SAHF / JL XX'/. Инструкция JL тестирует флаг знака и флаг переполнения, но не меняет последний. Чтобы исправить это, измените 'JL XX' на 'JS XX'.

Неожиданно (и в противоположность тому, что говорят руководства от Intel), частичная задержка регистра может случиться, если были изменены какие-то биты регистра флагов, а затем считаны неизмененные.

Код (Text):
  1.  
  2.         CMP EAX, EBX
  3.         INC ECX
  4.         JC  XX          ; задержка

но не при чтении только изменных битов:

Код (Text):
  1.  
  2.         CMP EAX, EBX
  3.         INC ECX
  4.         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) для сохранения значения флага, чтобы избежать задержки.

Примеры:

Код (Text):
  1.  
  2.     INC EAX   / PUSHFD      ; задержка
  3.     ADD EAX,1 / PUSHFD      ; нет задержки
  4.  
  5.     SHR EAX,1 / PUSHFD      ; задержка
  6.     SHR EAX,1 / OR EAX,EAX / PUSHFD   ; нет задержки
  7.  
  8.     TEST EBX,EBX / LAHF     ; задержка
  9.     AND  EBX,EBX / LAHF     ; нет задержки
  10.     TEST EBX,EBX / SETZ AL  ; нет задержки
  11.  
  12.     CLC / SETZ AL           ; задержка
  13.     CLD / SETZ AL           ; нет задержки

Потери при частичной задержки флагов примерно равны 4 тактам.

19.3 Задержки флагов после сдвигов и вращений

При чтении любого флагового бита после сдвига или вращения (кроме сдвига и вращения на 1, т.н. короткая форма) может возникнуть задержка, похожая на частичную задержку флагов:

Код (Text):
  1.  
  2.     SHR EAX,1 / JZ XX                ; нет задержки
  3.     SHR EAX,2 / JZ XX                ; задержка
  4.     SHR EAX,2 / OR EAX,EAX / JZ XX   ; нет задержки
  5.  
  6.     SHR EAX,5 / JC XX                ; задержка
  7.     SHR EAX,4 / SHR EAX,1 / JC XX    ; нет задержки
  8.  
  9.     SHR EAX,CL / JZ XX               ; задержка, даже если CL = 1
  10.  
  11.     SHRD EAX,EBX,1 / JZ XX           ; задержка
  12.     ROL EBX,8 / JC XX                ; задержка

Потери для этого вида задержек приблизительно равны 4 тактам.

19.4 Частичные задержки памяти

Частичная задержка памяти похожа на частичную задержку регистра. Она случается, когда вы смешиваете размеры данных применительно к одному адресу в памяти:

Код (Text):
  1.  
  2.         MOV BYTE PTR [ESI], AL
  3.         MOV EBX, DWORD PTR [ESI]        ; частичная задержка в памяти

Здесь случается задержка, потому что процессор должен скомбинировать байт, записанный из AL с тремя следующими байтами, которые были в памяти раньше, чтобы получить все четыре байта, необходимые для произведения записи в EBX. Потери приблизительно равны 7-8 тактов.

В отличии от частичных задержек регистра, частичная задержка памяти случается, когда вы записывает операнд в память, а затем читаете его часть, если она не начинается по тому же адресу:

Код (Text):
  1.  
  2.         MOV DWORD PTR [ESI], EAX
  3.         MOV BL, BYTE PTR [ESI]          ; нет задержки
  4.         MOV BH, BYTE PTR [ESI+1]        ; задержка

Вы можете избежать этой задержки, если измените последнюю лини на 'MOV BH, AH', но подобное решение невозможно в ситуации, подобной этой:

Код (Text):
  1.  
  2.         FISTP QWORD PTR [EDI]
  3.         MOV EAX, DWORD PTR [EDI]
  4.         MOV EDX, DWORD PTR [EDI+4]      ; задержка

Интересно, что частичная задержка памяти может также случиться при записи и чтении совершенно разны адресов, если у них одинаковое set-значение в разных банках кэша:

Код (Text):
  1.  
  2.         MOV BYTE PTR [ESI], AL
  3.         MOV EBX, DWORD PTR [ESI+4092]   ; нет задержки
  4.         MOV ECX, DWORD PTR [ESI+4096]   ; задержка
© Агнер Фог, пер. Aquila

0 742
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532