Анимация
JavaScript
|
Главная Библионтека
L2: 1 Теперь производится две операции одновременно, что приводит к лучшему спариванию. Приходится тестировать N на нечетность, и если оно нечетно, то делается дополнительная операция вне цикла, потому что внутри его можно сделать только четное количество операций. В цикле есть задержка AGI, генерируемая первой инструкцией MOV, потому что ЕСХ увеличивается на 1 в предыдущем такте, и поэтому для двух операций цикл занимает б тактов. 6.25.1.7. Реорганизация цикла для удаления задержки AGI (Р1 и РММХ). MOV ESI, [А] MOV ЕАХ, [N] MOV EDI, [В] XOR ЕСХ, ЕСХ LEA ESI, [ESI+4*EAX] ; указывает на конец массива А SUB ЕСХ, ЕАХ ; -N LEA EDI, [EDI+4*EAX] ; указывает на конец массива В JZ SHORT L3 TEST AL,1 ; тестируем N на нечетность JZ SHORT L2 MOV ЕАХ, [ESI+4*ECX] ; дополнительная операция, ; если счетчик нечетен NEG ЕАХ ; нет возможности к спариванию MOV [EDI+4*ECX-4], ЕАХ INC ЕСХ ; делаем счетчик четным JNZ SHORT L2 NOP ; добавляем NOP, если JNZ L2 не предсказуе!
Трюк заключается в том, чтобы найти пару инструкций, которые не используют счетчик цикла в качестве индекса, и реорганизовать цикл так, чтобы счетчик повышал свое значение на предыдущем такте. В этом случае реализация приближается к 5 тактам для двух операций, что близко наилучшему варианту. Если кэширование данных критично, можно улучшить скорость, объединив массивы А и В в один массив, так чтобы каждый B[i] находился непосредственно за соответствующим ему A[i]. Если структурированный массив выровнен по крайней мере на 8, тогда B[i] всегда будет находится на той же линии кэша, что и A[i], и всегда будет случаться кэш-промах при записи в B[i]. Это, конечно, сглаживается приростом производительности в других частях программы, но обязательно нужно взвесить все за и против. 6.25.1.8. Развертывание более чем на 2 (Р1 и РММХ). Можно рассмотреть возможность выпонение более чем двух операций за одно повторение, чтобы снизить количество действий, необходимых для организации цикла. Но так как в большинстве случаев время, требуемое для выполнения таких действий, можно снизить только на один такт, то развертывание цикла в четыре раза сохранит только 1/4 такта на операцию, что вряд ли стоит усилий, которые будут на это потрачены. Недостатки слишком большого развертывания цикла следующие: необходимо просчитать N MODULO R, где R - это коэффициент разворачивания, и сделать N MODULO R операций до или после основного цикла, чтобы выполнить недостающее количество операций. На это уйдет дополнительный код и плохо предсказуемые операции условного перехода. И, конечно, тело цикла станет больше; участок кода обычно выполняется в первый раз гораздо дольше (чем больше код, тем больше будут связанные с этим потери, особенно, если N невелико); • увеличение кода делает работу с кэшем менее эффективной; одновременная обработка нескольких 8- или 16-битных операндов в 32-битных регц стра(Р1 иРММХ). Если нужно обрабатывать массивы, состоящие из 8- или 16-битных операндов, появляются проблемы с развернутыми циклами, потому что нельзя спарить две операции доступа к памяти. Например, MOV AL,[ESI] / MOV BL,[ES1+1] не будут спариваться так как оба операнда находятся внутри одного и того же двойного слова памяти. Но есть продвинутый способ обрабатывать сразу два байта за раз в одном 32-битном регистре. Следующий пример добавляет 2 ко всем элементам массива байтов.
Этот цикл занимает 5 тактов для каждых 4 байт. Массив, разумеется, должен быть выравнен на 4. Если количество элементов в массиве не кратно четырем, тогда можно добавить в конец несколько байтов, чтобы сделать его размер кратным четырем. К нему будет происходить обращение за его концом, поэтому нужно убедиться, что тот не находится в конце сегмента, дабы избежать ошибки общей защиты. Следует обратить внимание, что перед прибавлением сохраняется старший бит каждого байта, чтобы не испортить их значение (если результат сложения превысит 256). Здесь используется XOR, а не ADD, при возврате младшего бита на место по той же самой причине. Инструкции ADD ESI,4 можно избежать, если использовать счетчик цикла в качестве индекса, как это сделано в примере пункта 6.25,1.3. Тем не менее, в результате количество инструкций в цикле будет нечетно, а значит, одна инструкция будет не спарена, и цикл по-прежнему займет 5 тактов. Нужно сделать инструкцию перехода неспаренной, что сохранит один такт после последней операции, если инструкция предсказывается неверно, и придется потратить дополнительный такт в коде пролога, чтобы установить указатель в конец массива и вычислить -N, поэтому эти два метода 5удут иметь одинаковую скорость. Метод, представленный в выше, является самым про стым и самым коротким. Следующий пример находит длину строки, заканчивающейся нулем, путем поискг первого байта, равного нулю. Он быстрее выполнится быстрее, чем команда REF SCASB. STRLEN PROC NEAR
L2 : STRLEN ENDP Здесь снова используется метод пересечения конца выполнения одной операции с началом выполнения другой, для улучшения спаривания. Здесь мы не стали разворачивать цикл, так как количество его повторений относительно невелико. Строка, конечно, должна быть выравнена на 4. Код будет считывать несколько байт за строкой, поэтому та не должна располагаться на фанице сегмента. Количество инсфукций в цикле нечетно, поэтому одна из них неспарена. Сделав неспаренной инструкцию перехода, а не какую-либо другую, мы сэкономим один такт, если инсфукция перехода будет предсказана неверно. Инсфукция TEST ЕСХ,00008080Н неспариваема. Однако можно использовать здесь спариваемую инсфукцию OR CH,CL вместо нее, но тогда придется поместить NOP или что-нибудь еще, чтобы избежать потерь из-за последовательных ршсфукций перехода. Другая проблема с OR CH,CL состоит в том, что она вызовет частичную задержку регистра на Рр Р2 или РЗ. Поэтому было выбран использование неспариваемой инструкции TEST. Обработка 4 байт за раз может быть довольно сложной. Код использует форму , которая генерирует ненулевое значение, для байта только тогда, когда он равен Это делает возможным протестировать все четыре байта за одну операцию. Этот алго ритм включает вычитание 1 из всех байтов (в инструкции LEA). Здесь не сохраняется" старший бит перед вычитанием, как это было сделано в предыдущем пункте, поэтом операция вычитания может испортить значение следующего байта в ЕАХ, но только если текущий равен нулю, но в этом случае не важно, что будет со следующим байтом Если искать нулевой байт в обратном порядке, пришлось бы считывать двойное слово повторно после обнаружения нуля, а затем протестировать все четыре байта, чтобы пай-ти последний ноль, или использовать BSWAP для изменения порядка байтов. Если нужет поиск байта с отличным от нуля значением, можно использовать XOR всех четырех байт со значением, которое нужно найти, а затем использовать метод при-веденный выше для поиска нуля. 6.25.1.9. Циклы с операциями ММХ (РММХ). Обработка нескольких операндов в одном регистре проще на ММХ-процессорах, потому что в них присутствуют специальные инструкции и регистры именно для этих целей. Возвращаясь к задаче добавления двойки ко всем байтам массива, можно воспользоваться расширенным набором инструкций ММХ. . data ALIGN 8 ADDENTS DQ 0202020202020202h
указываем байт, который нужно ; добавить 8 раз ; адрес массива байтов ; количество итераций сохраняем результат загружаем слагаемые обрабатываем 8 байт за одну опера сохраняем последний результат Инструкция сохранения помещена после инструкции управления циклом, чтоб] збежать задержки сохранения. . Этот цикл занимает 4 такта, потому что инструкция PADDB не спаривается с AD1 gSl, 8- (Инструкция ММХ с доступом к памяти не может спариваться с не-ММХ инст укцией или с другой инструкцией ММХ с доступом к памяти). Можно избавитьс от ADD ESI, 8, используя в качестве индекса ЕСХ, но это приведет к задержке AGI. Хак как затраты на управление циклом значительны, мы можем захотеть развернут цикл. .data ALIGN 8 ADDENTS DQ times A DD N DD . code MOVQ MOV MOV MOVQ MOVQ L3: PADDB PADDB MOVQ MOVQ MOVQ MOVQ ADD DEC JNZ EMMS 0202020202020202h MM2, [ADDENTS] ESI, [A] ECX, [N] MMO, MM2 MMl, MM2 MMO, lESl] MMl, [ESI+8] [ESI], MMO MMO, MM2 [ESI+8], MMl MMl, MM2 ESI, 16 ECX L3 значение, добавляемое к 8 байтам ; адрес массива байтов ; количество итераций Этот развернутый цикл занимает 6 тактов на выполнение при обработке 16 байтов, Инструкции PADDB не спариваются. Две ветви перемешаны, чтобы избежать задержки сохранения. Использование инструкций ММХ приводит к большим потерям, если вместе с ними используются инструкции с плавающей запятой, поэтому могут быть ситуации, когда 2-битные регистры будут предпочтительнее. 6.25.1.10. Циклы с инструкциями с плавающей запятой (Р1 и РММХ). Методы опти-"«изации циклов с инструкциями с плавающей запятой аналогичны инструкциям с цело-"•Чсленными аргументами, хотя инструкции с плавающей запятой могут пересекаться, но спариваться. Возьмем следующий код на языке С: 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 |