Анимация
JavaScript


Главная  Библионтека 

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

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

FDIV FXCH

SHR ЕАХ,1 INC ЕВХ CMC

FADD [x] FXCH FMUL [y]

1-39 1-2 3 3

такт 38-40 ; такт 38 ; такт 40-

такт такт такт такт такт

несовершенное спарива

и-конвейер) (V-конвейер, (U-конвейер) (V-конвейер) (не спаривается) (U-конвейер, ждет, пока FPU не освободи: (V-конвейер) 42 (и-конвейер, ждет результат FDIV)

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

Если нет ничего, чтобы поместить после инструкции плавающей запятой, которая может выполняться одновременно-с целочисленной инструкцией (FDIV, FSQRT), можно поместить чтение значения какой-нибудь переменной из памяти, которое может понадобиться в дальнейшем, чтобы она на 100% была в кэше.

[ЕВХ]

FDIV СМР FMUL

QWORD PTR [ESI],ESI QWORD PTR

[ESi:

Здесь загружается значение [ESI] в кэш, в то время как вычисляется FDIV (результат самой операции сравнения не важен).

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

Никаких потерь при использовании переменных из памяти в инструкциях плавающей запятой не происходит, потому что модуль арифметических вычислений стоит на один шаг дальше в конвейере, чем модуль чтения. Однако при сохранении данных может случиться задержка аналогичная задержке AG1: выполнение инструкции FST или FSTP с переменной в памяти в качестве операнда занимает два такта, но данные должны быть готовы на предыдущем такте, поэтому задержка будет в один такт, если значение, которое нужно сохранить, не будет готово еще на предыдущем такте.

[al]

; такт

FADD

[a2]

; такт

; такт

FADD

[b2]

; такт

FXCH

; такт

FSTP

[аЗ]

; такт

FSTP

[ЬЗ]

; такт

pSTP задерживается на один такт, потому что результат FADD не был готов дыдущем такте. Во многих случаях нельзя скрыть этот тип задержек без организа- кода с плавающей запятой в четыре ветви или помещения внутрь каких-то целочис-нных инструкций. Два такта на стадии выполнения инструкции FST(P) не могут спари-заться или пересекаться с любой другой последующей инструкцией.

Инструкции с целочисленными операндами, такими, как FIADD, FISUB, F1MUL, PjpjV, F1COM можно разделить на простые операции, чтобы улучшить пересекаемость выполнений инструкций:

FILD [а] ; cloclc cycle 1-3

FIMUL [b] ;clocl<:cycle4-9

Разделить на:

FILD FILD FMUL

[a] [b]

такты 1-3 такты 2-4 такты 5-7

В этом примере экономится два такта при сочетании выполнения двух инструкций FILD.

6.25. Оптамизация циклов (все процессоры)

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

Во всех приведенных ниже примерах предполагается, что все данные находятся в кэше первого уровня. Если скорость ограничивается из-за того, что данные загружаются в кэш, нет никакого смысла оптимизировать инструкции. Тогда лучше сконцентрироваться на правильной организации данных (разд. 6.7).

6.25.1 Циклы в Р1 и РММХ

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

Эта процедура на С может выглядеть так:

void CtiangeSign (int * А, int * В, int N)

int i ;

for (i = 0; KN; i--) B[i] = -A[i];

Переводя ее на ассемблер получается следующий код:



ChangeSign PROC NEAR

PUSH

PUSH

DWORD

[ESP+12]

DWORD

[ESP+16]

DWORD

[ESP+20]

ECX,

JECXZ

ESI ,

EDI ,

LODSD

STOSD

LOOP

; нет

соглашение о вызове

если объявлено cdecl

ChangeSign

ENDP

Это выглядит как достаточно красивое решение, но оно не самое оптимальное, потому что использует медленные неспариваемые инструкщ1и. Каждая итерация занимает 11 тактов при условии, что все данные находятся в кэше первого уровня.

6.25.1.1. Использование только спариваемых инструкций (Р1 и РММХ).

ECX, [N]

ESI, [A]

TEST

ECX, ECX

SHORT L2

EDI, [B]

EAX, [ESI]

EBX, EBX

(спаривается)

ESI , 4

EBX, EAX

(спаривается)

[EDI], EBX

EDI , 4

(спаривается)

(спаривается)

L2: д

Здесь используются только спариваемые инструкции. Теперь они занимают только такта на итерацию. Можно получить ту же самую скорость, не разделяя инструки NEG, но другие неспариваемые инструкции все же стоит разделить.

ESI, [A]

EDI, [B]

ECX, [N]

EDX, EDX

TEST

ECX, ECX

SHORT L2

EAX, [ESI+4*EDX]

[EDI+4*EDX], EAX

EDX, ECX

(спаривается)

(спаривается)

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

6.25.1.3. Пусть конечное значение счетчика равняется нулю (Р1 и РММХ). Можно избавиться от инструкции СМР в предыдущем коде, сделав так, чтобы последнее значение счетчика равнялось нулю, и использовать флаг нуля как знак того, что цикл закончен (как сделано в примере пункта 6.25.1.1). Один вариант заключается в том, чтобы испол-шпъ цикл задом наперед, взяв последний элемент первым. Тем не менее, кэш данных оптимизирован для их получения в прямом порядке, а не в обратном, поэтому если вероятны промахи кэша, следует задать значение счетчика -N и увеличивать его значение до нуля. Базовые регистры тогда должны указывать на конец массива, а не на его начало.

ESI, [A]

EAX, [N]

EDI, [B]

ECX, ECX

ESI, [ESI+4*EAX]

; указывает на конец

ECX, EAX

; -N

EDI, [EDH-4*EAX]

; указывает на конец

SHORT L2

EAX, [ESI+4*ECX]

; U

[EDI+4*ECX], EAX

; V (спаривается)

лассива В

6.25.1.2. Использование одного регистра как счетчика и индекса.



Однако цикл по-прежнему занимает 4 такта из-за плохой спариваемости инструкции (Если адреса и размеры массива являются константами, можно сохранить два регистра) А теперь посмотрим, как можно улучшить спариваемость.

6.25.1.4. Спаривание вычислений в цикле (Р1 и РММХ), Можно улучшить спарива. ние, перемешав инструкции вычисления с инструкциями управления циклом. Есть возможность поместить что-нибудь между INC ЕСХ и JNZ ЕГ, это должно быть чем-тх) что не влияет на флаг нуля. Инструкция MOV [ED1+4*ECX],EBX после INC ЕСХ сгенерирует задержку AGI, поэтому придется выбирать более искустный подход.

L1 : L2:

EAX, [N]

ECX, ECX

EAX, 2

; 4 * N

SHORT L3

ESI, [A]

EDI, [B]

; - 4 * N

ECX, EAX

ESI, EAX

; указывает на конец

EDI, EAX

; указывает на конец

SHORT L2

[EDI+ECX-4], EAX

; U

EAX, [ESI+ECX]

; V (спаривается)

EAX, -1

; U

ECX, 4

; V (спаривается)

; U

; V (спаривается)

[EDI+ECX-4], EAX

массива

Здесь используется альтернативный способ, чтобы посчитать отрицательное значение ЕАХ: инвертирование всех битов и инкремент. Причина, по которой задействуется этот метод, состоит в использовании трюка с инструкцией INC: 1NC не изменяет флаг переноса, в то время как ADD наоборот. ADD используется вместо ГМС, чтобы увеличивать счетчик цикла и управлять им с помошью флага переноса, а не флаг нуля. Тогда можно поместить INC ЕАХ между ними без вреда для флага переноса. Может возникнуть мысль, что следует использовать LEA EAX,[EAX-i-l] вместо INC ЕАХ, по крайней мере, он не меняет никаких флагов, но инструкция LEA сгенерирует задержку AGI, поэтому это не лучшее решение. Обратим внимание, что трюк с инструкцией INC, которая не меняет флаг переноса, будет полезен только на Р1 и РММХ, так как на РРго, Р2 и РЗ будут сгенерированы частичные задержки регистра флагов.

Здесь удалось добиться совершенного спаривания, и цикл теперь занимает только 3 такта. Нужно ли увеличивать значение счетчика цикла на 1 (как в пункта 6.25.1.3) или на 4 (как в сделано здесь) это дело вкуса, никакого влияния на время выполнения ци" это не окажет.

6.25.1.5. Пересечение времени выполнения одной операции с другой (Р1 и РММХ). \1етод, использованный в примере пункта 6.25.1.4, подходит далеко не во всех случаях, поэтому можно поискать другие способы улучшения спариваемости. Один из путей реорганизовать цикл - и сделать это так, чтобы конец выполнения одной операции пересекался с началом выполнения другой. Назовем это свернутым циклом. У свернутого цикла в его конце находится инструкция, выполнение которой будет закончено в следующем повторении. Фактически, в примере пункта 6.25.1.4 последняя команда MOV спаривается с первой. Исследуем этот метод поподробнее.

L3 ;

ESI, [A]

EAX, [N]

EDI, [B]

ECX, ECX

ESI, [ESI+4*EAX]

ECX, EAX

EDI, [EDI+4*EAX]

SHORT L3

EBX, EBX

EAX, [ESI+4*ECX]

SHORT L2

EBX, EAX

EAX, [ESI+4*ECX]

[EDI + 4*ECX-41 , EBX

EBX, 0

EBX, EAX

[EDI + 4*ECX-4] , EBX

указывает -N

указывает

на массив A

на массив В

U V U V U V

(спаривается) (спаривается) (спаривается)

Здесь начинается считывание второго значения до того, как сохраняется первое, и это, естественно, улучшает возможность спаривания. Инструкция MOV ЕВХ,0 была помещена между TNC ЕСХ и JNZ ЕГ не для того, чтобы улучшить спаривание, а для того, чтобы избежать задержки AGI.

6.25.1.6. Развертывание цикла (Р1 и РММХ). Один из самых часто использующихся способов улучшить спаривание - это сделать две операции на каждую итерацию, которых в этом случае станет в два раза меньше. Это называется развертыванием цикла.

MOV ESI, [А]

MOV ЕАХ, [N]

MOV EDI, [В]

XOR ЕСХ, ЕСХ

LEA ESI, [ESI+4*EAX] ; указывает на конец массиба А



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