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

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

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

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

Пример:

Код (Text):
  1.  
  2.    MOV EAX, [MEM1]
  3.    ADD EAX, [MEM2]
  4.    ADD EAX, [MEM3]
  5.    ADD EAX, [MEM4]
  6.    MOV [MEM5], EAX

В этом примере инструкция ADD генерирует 2 мопа, один для чтения из памяти (порт 2) и один для сложения (порт 0 или 1). Моп чтения может выполняться не по порядку, в то время как моп сложения дожна ждать, пока выполниться предыдущий моп. Эта цепочка зависимости занимает не очень много времени, так как каждое сложение требует только один такт. Но если в вашем коде содержатся медленные инструкции, такие как умножения, или еще хуже - деление, тогда вам определенно нужно сделать что-нибудь, чтобы убрать цепочку зависимости. Это можно сделать, используя разные приемники:

Код (Text):
  1.  
  2.    MOV EAX, [MEM1]         ; начало первой цепочки
  3.    MOV EBX, [MEM2]         ; начало второй цепочки  с другим приемником
  4.    IMUL EAX, [MEM3]
  5.    IMUL EBX, [MEM4]
  6.    IMUL EAX, EBX           ; в конце соединяем цепочки
  7.    MOV [MEM5], EAX

Здесь вторая инструкция IMUL может начаться до того, как будет завершено выполнение первой. Так как у инструкции IMUL вызывает задержку в 4 такта и полностью конвееризована, вы можете использовать до 4-х приемников.

Деление не конвееризовано, поэтому вы не можете делать то же самое со связанными делениями, но, разумеется, вы можете умножить все делители и сделать только одно деление в конце.

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

Код (Text):
  1.  
  2.    FLD [MEM1]         ; начинаем первую цепочку
  3.    FLD [MEM2]         ; начинаем вторую цепочку с другим приемником
  4.    FADD [MEM3]
  5.    FXCH
  6.    FADD [MEM4]
  7.    FXCH
  8.    FADD [MEM5]
  9.    FADD               ; соединяем цепочки в конце
  10.    FSTP [MEM6]

Вам потребуется для этого много инструкций FXCH, но не беспокойтесь: они стоят дешево. Инструкции FXCH обрабатываются в RAT с помощью переименования регистров, поэтому они не создают никакой назгрузки на порты выполнения. Тем не менее, FXCH генерирует один моп в RAT, ROB и в станции вывода из обращения.

Если цепочка зависимости очень длиная, вам может потребоваться три приемника:

Код (Text):
  1.  
  2.         FLD [MEM1]              ; начинаем первую цепочку
  3.         FLD [MEM2]              ; начинаем вторую цепочку
  4.         FLD [MEM3]              ; начинаем третью цепочку
  5.         FADD [MEM4]             ; третья цепочка
  6.         FXCH ST(1)
  7.         FADD [MEM5]             ; вторая цепочка
  8.         FXCH ST(2)
  9.         FADD [MEM6]             ; первая цепочка
  10.         FXCH ST(1)
  11.         FADD [MEM7]             ; третья цепочка
  12.  
  13.         FXCH ST(2)
  14.         FADD [MEM8]             ; вторая цепочка
  15.         FXCH ST(1)
  16.         FADD                    ; соединяем первую и третью цепочку
  17.         FADD                    ; результат соединяем со второй цепочкой
  18.         FSTP [MEM9]

Избегайте сохранения промежуточных данных в памяти и немедленного их считывания:

Код (Text):
  1.  
  2.         MOV [TEMP], EAX
  3.         MOV EBX, [TEMP]

Возникают потерю из-за попытки чтения из памяти до того, как завершена предыдущая запись туда же. В вышеприведенном примере измените последнюю инструкцию на 'MOV EBX, EAX' или поместите какие-нибудь инструкции между ними.

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

Код (Text):
  1.  
  2.         MOV EAX, [MEM1]
  3.         ADD EAX, [MEM2]
  4.         MOV [TEMP], EAX
  5.         FILD [TEMP]

Если вам нечего поместить между записью в TEMP и считыванием из него, вы можете использовать регистр плавающей запятой вместо EAX:

Код (Text):
  1.  
  2.         FILD [MEM1]
  3.         FIADD [MEM2]

Последовательные переходы, вызовы или возвраты также можно считать цепочками зависимости. Производительность этих инструкций равна одному переходу за два такта. Поэтому рекомендуется загружать микропроцессор какой-нибудь полезной работой между переходами. © Агнер Фог, пер. Aquila


0 790
archive

archive
New Member

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