Анимация
JavaScript
|
Главная Библионтека Ассемблер в задачах защиты информации ; нет нулевых байтов, продолжаем цик-
тестируем первые два байта сдвигаем вправо, если не впервых двух байтах используем флаг переноса, чтобы избежать ветвления высчитываем длину Этот Щ1КЛ занимает 3 такта на каждую итерацию, тестируя 4 байта. Строка, разумеется, должна быть выравнена на 4 байта. Код может считать несколько байт из памяти, находящейся за концом строки, поэтому строка не должна находиться на границе сегмента. Обработка 4-байт за раз может быть довольно сложной. Код использует формулу, которая генерирует ненулевое значение для байта только тогда, когда байт равен нулю. Это делает возможным протестировать все четыре байта за одну операцию. 6.25.2.4. Циклы с инструкциями ММХ (Р2 и РЗ). С помощью инструкций ММХ мож-
CMOVZ SHR SBB EMMS POP RET strlen ENDP EAX,ECX BL, 1 EAX,EDX В этом цикле 7 мопов для порта О и 1, что дает среднее время выполнения 3. такта на итерацию. Тесты показали 3.8 тактов, что говорит о том, что ROB справл) ется с ситуацией достаточно хорошо, несмотря на цепочку зависимости, котора равна 6 мопам. Тестирование 8 байтов за время меньшее, чем 4 такта гораздо быст рее, чем выполнение инструкции REPNE SCASB. 6.25.2.5. Циклы с инструкциями с плавающей запятой (РРго, Р2 и РЗ). Методы опт! мизирования циклов с плавающей запятой примерно те же, что и для целочисленны циклов, однако нужно остерегаться цепочек зависимости из-за долгого времени выпох нения инструкций. Следующий код на языке С под названием DAXPY: int i, n; double * X; double * Y; double DA; for (i=0; i<n; i++) Y[i] = Y[i] - DA * X[il; его ассемблер выглядит следующим образом: DSIZE = 8 ; размер данных (4 or 8) количество элементов указатель на X указатель на Y проверяем, равняется ли N нулю загружаем DA вне цикла ALIGN L1 ;
2 NOPa для выравнивания len=3 p2rESIwST0 len=3 pOlrESI len=2 pOrSTOrSTl len=3 p2rEDI, pOrSTO len=3 p4rST0, p3rEDI len=3 pOlrEDI len=l pOlrECXwF len=2 plrF сбрасываем DA DSIZE ALIGN LI : MOV MOV MOV LEA LEA NEG JZ FLD 16 FMUL . FSUBR FSTP FSTP ECX, ESI , EDI , ESI, EDI, ECX SHORT [N] [X] [Y] [ESI+DSIZE*ECX] [EDI+DSIZE*ECX] DSIZE PTR DSIZE PTR ST,ST (1) DSIZE PTR DSIZE PTR [DA] [ESI+DS1ZE*ECX] [EDI+DSIZE*ECX] [EDI+DSIZE*ECX1 ; размер данных (4 или ; количество элементов ; указатель на X ; указатель на Y ; указатель на конец массива ; указатель на конец массива ; -N проверяем, равняется ли N нулу. ; загружаем DA вне цикла ; 1еп=3 p2rESIrECXwST0 ; 1еп=2 pOrSTOrSTl ; 1еп=3 p2rEDIrECX, pOrSTO ; len=3 p4rST0, p3rEDIrECX ; len=l pOlrECXwF ; len=2 plrF ; сбрасываем DA Здесь мы используем тот же самый трюк, что и в примере 6.25.2.3. В идеальном случае этот цикл будет занимать 3 такта, но измерения говорят примерно о 3.5 ввиду длинной цепочки зависимости. Разворачивание цикла сэкономит немного. 6.25.2.6. Циклы с инструкциями ХММ (РЗ). Инструкции ХММ на РЗ позволяют оперировать четырьмя числами с плавающей запятой одинарной точности одновременно. Операнды должны быть выравнены на 16 байт. Алгоритм DAXPY не очень подходит для инструкций ХММ, потому что не так велика его точность, может не быть возможности выравнять операнды на границу 16 байт, поэтому потребуется дополнительный код, если количество операций не кратно четырем. Тем не менее, ниже приводится пример цикла с инструкциями ХММ.
L3 : L4 ;
; копируем -DA во все четыре ; позиции ; 1еп=4 2*p2rESIrECXwXMM0 len=3 pOlrwECXwF 1еп=3 2*pOrXMM0rXMMl len=3 pOlrECXwF len=5 2*p2rEDIrECX, 2*plrXMM0 len=5 2*p4rXMM0, 2*p3rEDIrECX ; len=2 plrF ; chec]<. if finished ; 1-3 операции пропущены, ; делаем еще четыре сохраняем еще два результата сохраняем еще один результат Цикл L1 занимает 5-6 тактов на 4 операции. Инструкции с ЕСХ были помещены до и после MULPS XMMO, ХММ Г, чтобы избежать задержки чтения регистра, которую сгенерировало бы чтение двух частей регистра ХММ1 вместе с ESI и EDI в RAT. Дополнительный код после L2 отвечает за ситуацию, когда N не делится на 4. Этот код может прочесть несколько байтов за пределами А и В. Это может задержать последнюю операцию, если в этих байтах не находились нормализованные числа с плавающей запятой. Желательно поместить в массив какие-нибудь дополнительные данные, чтобы сделать количество операций кратным 4 и избавиться от лищнего кода после L2. •26. Проблемные инструкции •26.1. XCHG (все процессоры) Инструкция XCHG регистр, [память] с точки зрения получания максимальной производительности опасна. По умолчанию эта инструкция имеет неявный префикс LOCK, о не дает ей загружаться в кэш. Поэтому выполнение данной инструкции отнимает нь много времени, и ее следует избегать. Цепочка зависимости длиной в 10 тактов, но цикл занимает только 4 такта на итерацц потому что он может начать новую операцию еще до того, как выполнена предыдущ Цель выравнивания - предотвратить 16-байтную границу в последнем БДИ. 6.26.2. Вращение через флаг переноса (все процессоры) RCR и RCL, сдвигающие аргумент более, чем один бит, медленны, и их следует избегать. 6.26.3. Строковые инструкции (все процессоры) Строковые инструкции без префикса повторения слишком медленны, и их следует заменить более простыми инструкциями. То же самое относится к LOOP на всех процессорах, и к JECXZ на PI и РММХ. Инструкции REP MOVSD и REP STOSD относительно быстры, если число повторений не слишком мало. Желательно всегда использовать их DWORD версии и, где это возможно, источник и приемник выравнивать на 8 байт. Некоторые способы песылки данных оказываются быстрее в определенных условиях (подробнее см. пункт разд. 6.27.8.). Следует обратить внимание, что пока инструкция REP MOVS записывает слово в приемник, она считывает следующее слово из источника на том же такте. Поэтому может конфликт банков кэша, если биты 2 - 4 у этих двух адресов совпадают. Другими словами, возникнут неизбежные потери в один такт на итерацию, если ESI+(pa3Mep сло-Ba)-EDI кратно 32. Самый простой путь избежать конфликтов банков кэша - это использовать версию DWORD и выравнивать источник и приемник на 8. Инструкции MOVSB или MOVSW имеют самую низкую скорость выполнения даже в 16-битном. REP MOVS и REP STOS могут выполняться очень быстро, если перемещать блок данных размером в одну строку кэша за раз (РРго, Р2 и РЗ): ffl источник и приемник должны быть выравнены на 8; а должно быть задано направление пересылки «вперед» (очищен флаг направления, CLD); Я счетчик (ЕСХ) должен иметь значение равное или большее 64; разница между ED1 и ESI должна быть численно больше или равна 32. В этих условиях количество мопов будет примерно равно 215+2* ЕСХ для REP MOVSD и 185+1.5*ЕСХ для REP STOSD, что дает примерную скорость в 5 байтов в такт для обоих инструкций, что в три раза больше, в том случае если какое-нибудь из вышеприведенных условий не будет соблюдено. Версии этой инструкции для байтов и слов также выигрывают от соблюдений данных условий, но они менее эффективны, чем версии для двойных слов. REP STOSD более оптимальна в одних и тех же условиях, что и REP MOVSD. REP LOADS, REP SCAS и REP CMPS не оптимальны, и их можно заменить на соответствующие циклы. См. п. 6.25.1.9, 6.25.2.7 и 6.25.2.8 для поиска альтернатив. Инстру! ции REPNE SCASB. REP CMPS могут вызывать конфликты баноков кэша, если биты 2 одинаковы в ESI и EDI. 6.26.4. Тестирование битов (все процессоры) Инструкции ВТ, ВТС, BTR и BTS следует заменять инструкциями TEST, AND, OR, XOR или сдвига на процессорах Р1 и РММХ. На РРго, Р2 и РЗ битовых тестов операндов из памяти следует также избегать. 6.26.5. Целочисленное умножение (все процессоры) Целочисленное умножение занимает до 9 тактов на Р1 и РММХ и до 4 тактов на РРго, Р2 и РЗ. Поэтому часто выгоднее бывает заменить умножение на константу на комбинацию других инструкций, таких, как SHL, ADD, SUB и LEA. imul еах,10 Можно заменить на mov евх,еах / add еах,еах / shl евх,3 / add еах,евх lea еах,[еах+4*еах] / add еах,еах Умножение чисел с плавающей запятой выполняется быстрее, чем целочисленное умножение на процессорах Р1 и РММХ. Но время, затрачиваемое на преобразование целых чисел в числа с плавающей запятой и обратно, обычно больше, чем время, сэкономленное в результате использования умножения с плавающей запятой, не считая тех случаев, когда количество конвертации несравнимо с количеством умножений. Умножение ММХ достаточно быстро, но доступно только для 16-битных операндов. 6.26.6. Инструкция WAIT (все процессоры) Зачастую можно добиться повышения скорости исполнения пренебрегнув инструкцией WAIT. Эта инструкция имеет три функции: Ранние сопроцессоры 8086 требовали WAIT перед каждой инструкцией с плавающей запятой, чтобы убедиться, что сопроцессор готов ее получить. Если не требуется совместимость с 8087, следует указать ассемблеру, чтобы он не помечал WAIT, задав опцию генерации кода для более современного процессора. Эмулятор вычислений с плавающей запятой 8087 также вставляет инструкции WAIT, поэтому следует указать ассемблеру не генерировать код эмуляции 8087, если это действительно не важно. WAIT используется для координирования доступа к памяти между модулем вычисле-с плавающей запятой и модулем целочисленных вычислений. 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 |