Анимация
JavaScript
|
Главная Библионтека ).26.15. Битовое сканирование (Р1 и РММХ) BSF и BSR - наиболее тяжело оптимизируемые инструкции на Р1 и РММХ, они занимают приблизительно 11+2*п тактов, где п равен количеству пропущенных нулей. Следующий код эмулирует BSR ЕСХ,ЕАХ:
со старым 286 BS1; Следующий код эмулирует BSF БСХ,ЕАХ:
BS2 ; Этот код не следует использовать на РРго, Р2 и РЗ, у которых инструкции битового сканирования занимают только 1 или 2 такта и где данный код вызовет около двух заде-жек чтения памяти. 6.26.16. FLDCW (РРго, Р2 и РЗ) На РРго, Р2 и РЗ инструкция FLDCW вызывает серьезную задержку, если за ней следует любая инструкция с плавающей запятой, считывающая контрольное слово (как делают практически все инструкции плавающей запятой). Компиляторы С или С++ часто генерируют множество инструкций FLDCW, потому что конвертация чисел с плавающей запятой в целые числа делается с помощью усечения, в то время как другие инструкции с плавающей запятой используют округление. При переводе на ассемблер, можно улучшить код, использовав округление вместо усечения, где это возможно, или убрав FLDCW из цикла, если требуется усечение внутри него. См. п. 6.27.5, чтобы узнать, как сконвертировать число с плавающей запятой в целое без изменения контрольного слова. 6.27. Специальные темы 6.27.1. Инструкция LEA (все процессоры) Инструкция LEA полезна для самых разных целей, потому что она умеет делать сдвиг, два сложения и перемещение за один такт: LEA ЕАХ, [ЕВХ + 8*ЕСХ- 1 0 0 0 ] гораздо быстрее, чем MOV ЕАХ,ЕСХ / SHL ЕАХ,3 / ADD ЕАХ,ЕВХ / SUB ЕАХ,1000 Инструкцию LEA можно использовать, чтобы делать сложение или сдвиг без изменения флагов. Источник и назначение не обязательно должны быть размером в слово, поэтому LEA ЕАХ,[ВХ] может стать возможной заменой для MOVZX ЕАХ,ВХ, хотя на многих процессорах это не совсем оптимально. Как бы то ни было, следует знать, что инструкция LEA вызывает задержку AGI на Р1 и РММХ, если она использует базовый или индексный регистр, в которой была произведена запись в предыдущем такте. Так как инструкция LEA спариваема в V-конвейере на Р1 и РММХ, а инструкции сдвига - нет, вы можно использовать LEA в качестве замены SHL на 1, 2 или 3 позиции, чтобы инструкция выполнялась в V-конвейере. У 32-битных конвейеров нет документированното режима адресации с индексным регистром, поэтому инструкция LEA ЕАХ,[ЕАХ*2] на самом деле записывается как LEA ЕАХ,[ЕАХ*2+00000000] с 4-байтовым смещением. Можно уменьшить размер инструкции, написав LEA ЕАХ,[ЕАХ+ЕАХ] или, что еще лучше, ADD ЕАХ,ЕАХ. Последний вариант не приведет к задержке AG1 на Р1 и РММХ. Если случилось так, что есть ре- Причина этой неспариваемости, вероятно, состоит в том, что первый байт двухбайтной инструкции такой же, что и для неспариваемых инструкций, и процессор не может проверить второй байт во время проверки спариваемости. Ассемблер в задачах защиты информацу гистр, равный нулю (например, счетчик цикла после последнего прохода), его можно использовать как базовый регистр, чтобы снизить размер кода: LEA ЕАХ, [ЕВХ*4] ; 7 байтов LEA ЕАХ, [ЕСХ + ЕВХ*41 ; 3 байтов 6.27.2. Деление (все процессоры) Деление отнимает очень много времени. На РРго, Р2 и РЗ целочисленное деление занимает, соответственно, 19, 23 или 39 для байта, слова и двойного слова. На Р1 и РММХ беззнаковое целочисленное деление занимает приблизительно тоже время, хотя деление со знаком отнимает немного больше времени. Поэтому более предпочтительно использовать операнды малого размера, которые не вызовут переполнения, даже если это будет стоить префикса размера операнда, и использовать по возможности беззнаковое деление. 6.27.2.1. Целочисленное деление на константу (все процессоры). Целочисленное деление на степень двух можно сделать, сдвигая значение вправо. Деление беззнакового целого числа на 2N: SHR ЕАХ, N Деление целого числа со знаком на 2N: AND EDX, (1 SHL N) -1 ; или SHR EDX, 32-N ADD EAX, EDX SAR EAX, N Альтернативный SHR короче, чем AND if N > 7, но она может попасть только в порт О (или U-конвейер), в то время как AND может попасть как в порт О, так и в порт 1 (U- или V-конвейер). Делением на константу можно получить число обратное делению. Чтобы произвести беззнаковое целочисленное деление q = х / d, сначала нужно вычислить число, обратное делителю, f = 2г / d, где г определяет позицию двоично-десятичной точки (точка основания системы счисления). Затем нужно умножить х на f и сдвинуть полученный результат на г позиций вправо. Максимальное значение г равно 32-i-b, где b равно числу двоичных цифр в d минус 1 (Ь - это самое большое целое число, для которого 2Ь <= d). Для покрытия максильного количества возможных значений делимого х используется г = 32+Ь. Этот метод требует некоторых приемов, чтобы компенсировать ошибки округления. Следующий алгоритм дает верные результаты для деления беззнакового целого чила с усечением, т. е. тот же результат, что дает инструкция DIV (Terje Mathisen изобрел этот метод). b = (количество значимых битов в d) - 1 г = 32 + b f = 2г / d Глава 6. Оптимизация для процессоров семейства Pe nt um Если f - целое число, тогда d - это степень от 2: переходим к случаю А. Если f - не целое число, тогда проверяем, меньше ли дробная часть f 0.5. Если дробная часть f < 0.5: переходим к случаю В. Если дробная часть f > 0.5: переходим к случаю С. случай А: (d = 2b) результат = х SHR b случай В: (дробная часть f < 0.5) округляем f вниз до ближайшего целого числа результат = ((х+1) * f) SHR г случай С; (дробная часть f > 0.5) округляем f вверх до ближайшего целого числа результат = (х * f) SHR г Пример рассмотрим деление на 5. -1 = 2 5 = 00000101b. b = (количество значимых двоичных чисел) г = 32 + 2 = 34 г. . . f = 234 / 5 = 3435973836.8 = ОСССССССС.ССС...(hexadecimal) Дробная часть больше, чем половина: используем случай С. Округляем f ввер до OCCCCCCCDh. Следующий код делит ЕАХ на 5 и возвращает результат в EDX: MOV EDX,OCCCCCCCDh MUL EDX SHR EDX,2 После умножения EDX содержит значение, сдвинутое вправо на 32. Так как г = 3. нужно сдвинуть еще на 2, чтобы получить окончательный результат. Чтобы поделить i 10, нужно всего лишь заменить последнюю строку на SHR EDX,3. В случае В будет следующее: INC MOV MUL SHR ЕАХ EDX, f EDX EDX,b Этот код работает для всех значений х, кроме OFFFFFFFFH, которое дает ноль из-переполнения в инструкции INC. Если возможно, что х = OFFFFFFFFH, тогда следу заменить этот код на: MOV EDX,f ADD ЕАХ,1 JC DOVERFL MUL EDX DOVERFL:SHR EDX,b Если значение х отраничено, следует использовать меньшее значение г, т. е. меньщее количество цифр. Может быть несколько причин для того, чтобы сделать это: можно установить г = 32 и избежать SHR EDX,b в конце; можно установить г = 16+Ь и использовать инструкции умножения, которые дат 32-х битный результат, вместо 64-х битного. Тогда можно освободить регистр EDX IMUL EAX,OCCCDh / SHR ЕАХ,18; в можно выбрать значение г, которое будет чаще приводить к случаю С, а не В, чтобы избежать инструкции INC ЕАХ. Максимальное значение х в этих случаях равно, по крайней мере, 2г-Ь, иногда больще. Нужно проделывать систематические тесты, чтобы узнать точное максимальное значение X, при котором код будет работать корректно. Можно заменить медленную инструкцию умножения более быстрыми инструкциями, как было показаноно в п. 6.26.5. Следующий пример делит ЕАХ на 10 и возвращает результат в ЕАХ. Здесь выбрано г=17, а не 19, потому что это дает код, который легче оптимизировать, и он покрывает
Проведенные тесты показывают, Что этот код работает правильно для всех значений х< 10004Н. 6.27.2.2. Повторяемое деление целого цисла на одно и то же значение (все процессоры). Если делитель неизвестен во время ассемблирования программы, но деление осуществляется на одно и то же число несколько раз, можно использовать следующий метод: код должен определить, с каким случаем (А, В и С) он имеет дело, и вычислить f ДО выполнения операции деления. Нижеследующий код показывает, как делать несколько делений на одно и то же число (беззнаковое деление с усечением). Сначала вызывается SET DIVISOR, чтобы установить делитель и обратное ему число, затем вызывается DIVIDEFIXED для каждого значения, которое нужно разделить на один и тот же делитель. . data RECIPROCAL DIVISOR DD ? CORRECTION DD ? gSHlFT DD ? code SET DIVISOR PROC NEAR PUSH EBX ; округленное число, обратное делителю MOV BSR MOV JZ SHL MOV CMP MOV JE DIV SHR XOR CMP SETBE MOV XOR ADD MOV POP RET CASE A: MOV POP RET SET DIVISOR EBX,EAX ECX,EAX EDX, 1 ERROR EDX,CL [BSHIFT],ECX EAX,EDX EAX, 0 SHORT CASE A EBX, 1 ECX,ECX EDX,EBX [CORRECTION],ECX ECX, 1 EAX,ECX ; случай A: -1, случай В: 1, случай С: О ; количество бит в делителе - 1 ; делитель в ЕАХ ; b = количество бит в делителе - 1 ; ошибка: делитель равен нулю ; 2"Ь ; сохраняем b ; делитель - степень от 2 ; 2М32 + Ь) / d ; делитель / 2 ; сравниваем остаток с делителем/2 ; 1 если случай В коррекция возможных ошибок округления ; добавляем 1 если случай С RECIPR0CAL DIVIS0R1,ЕАХ ; округленное число, обратное ; делителю [CORRECTION],-1 ЕВХ ENDP DIVIDE FIXED PROC NEAR MOV EDX, [CORRECTION] MOV ECX,[BSHIFT] TEST EDX,EDX JS SHORT DSHIFT ADD EAX,EDX ; JC SHORT DOVERFL MUL [RECIPROCAL DIVISOR] MOV DSHIFT: SHR RET DOVERFL:MOV EAX,EDX EAX,CL запоминаем, что у нас случай А делимое в ЕАХ, результат в ЕАХ ; делитель - степень от 2 коррекция возможных ошибок округления ; коррекция при переполнении ; умножаем на число, обратное делителю сдвигаем на количество бит ЕАХ,[RECIPR0CAL DIVIS0R1 ; делимое = OFFFFFFFFH EAX,CL ; делаем деление с помощью сдвига 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 |