В компиляторе конструкции, снижение прочности является оптимизацией компилятора, где дорогостоящие операции заменяются эквивалентными, но менее дорогие операциями. Классический пример уменьшения силы преобразует «сильные» умножения внутри цикла в «более слабые» сложения - что часто происходит при адресации массивов. ( Купер, Симпсон и Вик, 1995, стр. 1)
Примеры снижения прочности включают:
Большая часть времени выполнения программы обычно тратится на небольшой участок кода (называемый активной точкой ), и этот код часто находится внутри цикла, который выполняется снова и снова.
Компилятор использует методы для идентификации циклов и распознавания характеристик значений регистров в этих циклах. Для снижения прочности компилятор интересует:
Инварианты цикла - это, по сути, константы внутри цикла, но их значение может изменяться вне цикла. Индукционные переменные изменяются на известные величины. Условия относятся к конкретному циклу. Когда циклы вложены, индукционная переменная во внешнем цикле может быть инвариантом цикла во внутреннем цикле.
Снижение силы ищет выражения, включающие инвариант цикла и индукционную переменную. Некоторые из этих выражений можно упростить. Например, умножение инварианта цикла c
и переменной индукцииi
c = 7; for (i = 0; i lt; N; i++) { y[i] = c * i; }
можно заменить последовательными более слабыми добавками
c = 7; k = 0; for (i = 0; i lt; N; i++) { y[i] = k; k = k + c; }
Ниже приведен пример, который сократит все циклы умножения, возникающие в результате вычислений адреса индексации массива.
Представьте себе простой цикл, который устанавливает массив в единичную матрицу.
for (i = 0; i lt; n; i++) { for (j = 0; j lt; n; j++) { A[i,j] = 0.0; } A[i,i] = 1.0; }
Компилятор будет рассматривать этот код как
0010 ; for (i = 0, i lt; n; i++) 0020 ; { 0030 r1 = #0 ; i = 0 0040 G0000: 0050 load r2, n ; i lt; n 0060 cmp r1, r2 0070 bge G0001 0080 0090 ; for (j = 0; j lt; n; j++) 0100 ; { 0110 r3 = #0 ; j = 0 0120 G0002: 0130 load r4, n ; j lt; n 0140 cmp r3, r4 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0180 load r7, n 0190 r8 = r1 * r7 ; calculate subscript i * n + j 0200 r9 = r8 + r3 0210 r10 = r9 * #8 ; calculate byte address 0220 fr3 = #0.0 0230 fstore fr3, A[r10] 0240 0250 r3 = r3 + #1 ; j++ 0260 br G0002 0270 ; } 0280 G0003: 0290 ; A[i,i] = 1.0; 0300 load r12, n ; calculate subscript i * n + i 0310 r13 = r1 * r12 0320 r14 = r13 + r1 0330 r15 = r14 * #8 ; calculate byte address 0340 fr4 = #1.0 0350 fstore fr4, A[r15] 0360 0370 ; i++ 0380 r1 = r1 + #1 0390 br G0000 0400 ; } 0410 G0001:
Это выражает двумерный массив A как одномерный массив размером n * n, так что всякий раз, когда код высокого уровня выражает A [x, y], он будет внутренне A [(x * n) + y] для любого заданы допустимые индексы x и y.
Компилятор начнет делать много оптимизаций - не только сокращение силы. Выражения, которые являются постоянными (инвариант) в пределах цикла будет поднят из петли. Константы могут загружаться вне обоих циклов, например, в регистры с плавающей запятой fr3 и fr4. Признание того, что некоторые переменные не изменяются, позволяет объединять регистры; n является постоянным, поэтому r2, r4, r7, r12 можно поднимать и свертывать. Общее значение i * n вычисляется в (поднятых) r8 и r13, поэтому они разрушаются. Самый внутренний цикл (0120-0260) был сокращен с 11 до 7 промежуточных инструкций. Единственное умножение, которое остается в самом внутреннем цикле, - это умножение строки 0210 на 8.
0010 ; for (i = 0, i lt; n; i++) 0020 { 0030 r1 = #0 ; i = 0 0050 load r2, n 0130 ; load r4, n killed; use r2 0180 ; load r7, n killed; use r2 0300 ; load r12, n killed; use r2 0220 fr3 = #0.0 0340 fr4 = #1.0 0040 G0000: 0060 cmp r1, r2 ; i lt; n 0070 bge G0001 0080 0190 r8 = r1 * r2 ; calculate subscript i * n 0310 ; r13 = r1 * r2 killed; use r8 ; calculate subscript i * n 0090 ; for (j = 0; j lt; n; j++) 0100 { 0110 r3 = #0 ; j = 0 0120 G0002: 0140 cmp r3, r2 ; j lt; n 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0200 r9 = r8 + r3 ; calculate subscript i * n + j 0210 r10 = r9 * #8 ; calculate byte address 0230 fstore fr3, A[r10] 0240 0250 r3 = r3 + #1 ; j++ 0260 br G0002 0270 } 0280 G0003: 0290 ; A[i,i] = 1.0; 0320 r14 = r8 + r1 ; calculate subscript i * n + i 0330 r15 = r14 * #8 ; calculate byte address 0350 fstore fr4, A[r15] 0360 0370 ;i++ 0380 r1 = r1 + #1 0390 br G0000 0400 } 0410 G0001:
Осталось еще провести оптимизацию. Регистр r3 - это основная переменная во внутреннем цикле (0140-0260); он увеличивается на 1 каждый раз в цикле. Регистр r8 (который является инвариантным для внутреннего цикла) добавляется к r3. Вместо использования r3 компилятор может исключить r3 и использовать r9. Контуром вместо того, чтобы управлять с помощью r3 = 0 до n-1, можно управлять с помощью r9 = r8 + 0 до r8 + n-1. Это добавляет четыре инструкции и уничтожает четыре инструкции, но внутри цикла на одну инструкцию меньше.
0110 ; r3 = #0 killed ; j = 0 0115 r9 = r8 ; new assignment 0117 r20 = r8 + r2 ; new limit 0120 G0002: 0140 ; cmp r3, r2 killed ; j lt; n 0145 cmp r9, r20 ; r8 + j lt; r8 + n = r20 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0200 ; r9 = r8 + r3 killed ; calculate subscript i * n + j 0210 r10 = r9 * #8 ; calculate byte address 0230 fstore fr3, A[r10] 0240 0250 ; r3 = r3 + #1 killed ; j++ 0255 r9 = r9 + #1 ; new loop variable 0260 br G0002
Теперь r9 - это переменная цикла, но она взаимодействует с умножением на 8. Здесь мы можем немного уменьшить силу. Умножение на 8 можно свести к нескольким последовательным прибавлениям 8. Теперь внутри цикла нет умножений.
0115 r9 = r8 ; new assignment 0117 r20 = r8 + r2 ; new limit 0118 r10 = r8 * #8 ; initial value of r10 0120 G0002: 0145 cmp r9, r20 ; r8 + j lt; r8 + n = r20 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0210 ; r10 = r9 * #8 killed ; calculate byte address 0230 fstore fr3, A[r10] 0240 0245 r10 = r10 + #8 ; strength reduced multiply 0255 r9 = r9 + #1 ; loop variable 0260 br G0002
Регистры r9 и r10 (= 8 * r9) не нужны; r9 можно исключить в цикле. В цикле теперь 5 инструкций.
0115 ; r9 = r8 killed 0117 r20 = r8 + r2 ; limit 0118 r10 = r8 * #8 ; initial value of r10 0119 r22 = r20 * #8 ; new limit 0120 G0002: 0145 ; cmp r9, r20 killed ; r8 + j lt; r8 + n = r20 0147 cmp r10, r22 ; r10 = 8*(r8 + j) lt; 8*(r8 + n) = r22 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0230 fstore fr3, A[r10] 0240 0245 r10 = r10 + #8 ; strength reduced multiply 0255 ; r9 = r9 + #1 killed ; loop variable 0260 br G0002
Вернуться ко всей картине:
0010 ; for (i = 0, i lt; n; i++) 0020 { 0030 r1 = #0 ; i = 0 0050 load r2, n 0220 fr3 = #0.0 0340 fr4 = #1.0 0040 G0000: 0060 cmp r1, r2 ; i lt; n 0070 bge G0001 0080 0190 r8 = r1 * r2 ; calculate subscript i * n 0117 r20 = r8 + r2 ; limit 0118 r10 = r8 * #8 ; initial value of r10 0119 r22 = r20 * #8 ; new limit 0090 ; for (j = 0; j lt; n; j++) 0100 { 0120 G0002: 0147 cmp r10, r22 ; r10 = 8*(r8 + j) lt; 8*(r8 + n) = r22 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0230 fstore fr3, A[r10] 0240 0245 r10 = r10 + #8 ; strength reduced multiply 0260 br G0002 0270 } 0280 G0003: 0290 ; A[i,i] = 1.0; 0320 r14 = r8 + r1 ; calculate subscript i * n + i 0330 r15 = r14 * #8 ; calculate byte address 0350 fstore fr4, A[r15] 0360 0370 ;i++ 0380 r1 = r1 + #1 0390 br G0000 0400 } 0410 G0001:
Теперь во внешнем цикле есть четыре умножения, увеличивающих r1. Регистр r8 = r1 * r2 в 0190 можно уменьшить, установив его перед входом в цикл (0055) и увеличив его на r2 внизу цикла (0385).
Значение r8 * 8 (0118) можно уменьшить, инициализировав его (0056) и добавив к нему 8 * r2 при увеличении r8 (0386).
Регистр r20 увеличивается на инвариант / константу r2 каждый раз в цикле 0117. После увеличения он умножается на 8 для создания r22 в 0119. Это умножение может быть уменьшено, добавляя 8 * r2 каждый раз в цикле..
0010 ; for (i = 0, i lt; n; i++) 0020 { 0030 r1 = #0 ; i = 0 0050 load r2, n 0220 fr3 = #0.0 0340 fr4 = #1.0 0055 r8 = r1 * r2 ; set initial value for r8 0056 r40 = r8 * #8 ; initial value for r8 * 8 0057 r30 = r2 * #8 ; increment for r40 0058 r20 = r8 + r2 ; copied from 0117 0058 r22 = r20 * #8 ; initial value of r22 0040 G0000: 0060 cmp r1, r2 ; i lt; n 0070 bge G0001 0080 0190 ; r8 = r1 * r2 killed ; calculate subscript i * n 0117 ; r20 = r8 + r2 killed - dead code 0118 r10 = r40 ; strength reduced expression to r40 0119 ; r22 = r20 * #8 killed ; strength reduced 0090 ; for (j = 0; j lt; n; j++) 0100 { 0120 G0002: 0147 cmp r10, r22 ; r10 = 8*(r8 + j) lt; 8*(r8 + n) = r22 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0230 fstore fr3, A[r10] 0240 0245 r10 = r10 + #8 ; strength reduced multiply 0260 br G0002 0270 } 0280 G0003: 0290 ; A[i,i] = 1.0; 0320 r14 = r8 + r1 ; calculate subscript i * n + i 0330 r15 = r14 * #8 ; calculate byte address 0350 fstore fr4, A[r15] 0360 0370 ;i++ 0380 r1 = r1 + #1 0385 r8 = r8 + r2 ; strength reduce r8 = r1 * r2 0386 r40 = r40 + r30 ; strength reduce expression r8 * 8 0388 r22 = r22 + r30 ; strength reduce r22 = r20 * 8 0390 br G0000 0400 } 0410 G0001:
Это оставляет два цикла только с одной операцией умножения (на 0330) во внешнем цикле и без умножений во внутреннем цикле.
0010 ; for (i = 0, i lt; n; i++) 0020 { 0030 r1 = #0 ; i = 0 0050 load r2, n 0220 fr3 = #0.0 0340 fr4 = #1.0 0055 r8 = r1 * r2 ; set initial value for r8 0056 r40 = r8 * #8 ; initial value for r8 * 8 0057 r30 = r2 * #8 ; increment for r40 0058 r20 = r8 + r2 ; copied from 0117 0058 r22 = r20 * #8 ; initial value of r22 0040 G0000: 0060 cmp r1, r2 ; i lt; n 0070 bge G0001 0080 0118 r10 = r40 ; strength reduced expression to r40 0090 ; for (j = 0; j lt; n; j++) 0100 { 0120 G0002: 0147 cmp r10, r22 ; r10 = 8*(r8 + j) lt; 8*(r8 + n) = r22 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0230 fstore fr3, A[r10] 0240 0245 r10 = r10 + #8 ; strength reduced multiply 0260 br G0002 0270 } 0280 G0003: 0290 ; A[i,i] = 1.0; 0320 r14 = r8 + r1 ; calculate subscript i * n + i 0330 r15 = r14 * #8 ; calculate byte address 0350 fstore fr4, A[r15] 0360 0370 ;i++ 0380 r1 = r1 + #1 0385 r8 = r8 + r2 ; strength reduce r8 = r1 * r2 0386 r40 = r40 + r30 ; strength reduce expression r8 * 8 0388 r22 = r22 + r30 ; strength reduce r22 = r20 * 8 0390 br G0000 0400 } 0410 G0001:
В строке 0320 r14 представляет собой сумму r8 и r1, а r8 и r1 увеличиваются в цикле. Регистр r8 увеличивается на r2 (= n), а r1 - на 1. Следовательно, r14 увеличивается на n + 1 каждый раз в цикле. Последнее умножение цикла в 0330 можно уменьшить, добавляя (r2 + 1) * 8 каждый раз в цикле.
0010 ; for (i = 0, i lt; n; i++) 0020 { 0030 r1 = #0 ; i = 0 0050 load r2, n 0220 fr3 = #0.0 0340 fr4 = #1.0 0055 r8 = r1 * r2 ; set initial value for r8 0056 r40 = r8 * #8 ; initial value for r8 * 8 0057 r30 = r2 * #8 ; increment for r40 0058 r20 = r8 + r2 ; copied from 0117 0058 r22 = r20 * #8 ; initial value of r22 005A r14 = r8 + r1 ; copied from 0320 005B r15 = r14 * #8 ; initial value of r15 (0330) 005C r49 = r2 + #1 005D r50 = r49 * #8 ; strength reduced increment 0040 G0000: 0060 cmp r1, r2 ; i lt; n 0070 bge G0001 0080 0118 r10 = r40 ; strength reduced expression to r40 0090 ; for (j = 0; j lt; n; j++) 0100 { 0120 G0002: 0147 cmp r10, r22 ; r10 = 8*(r8 + j) lt; 8*(r8 + n) = r22 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0230 fstore fr3, A[r10] 0240 0245 r10 = r10 + #8 ; strength reduced multiply 0260 br G0002 0270 } 0280 G0003: 0290 ; A[i,i] = 1.0; 0320 ; r14 = r8 + r1 killed ; dead code 0330 ; r15 = r14 * #8 killed ; strength reduced 0350 fstore fr4, A[r15] 0360 0370 ;i++ 0380 r1 = r1 + #1 0385 r8 = r8 + r2 ; strength reduce r8 = r1 * r2 0386 r40 = r40 + r30 ; strength reduce expression r8 * 8 0388 r22 = r22 + r30 ; strength reduce r22 = r20 * 8 0389 r15 = r15 + r50 ; strength reduce r15 = r14 * 8 0390 br G0000 0400 } 0410 G0001:
Впереди еще много всего. Постоянное сворачивание распознает, что r1 = 0 в преамбуле, поэтому несколько инструкций будут очищены. Регистр r8 в цикле не используется, поэтому он может исчезнуть.
Кроме того, r1 используется только для управления контуром, поэтому r1 можно заменить другой переменной индукции, такой как r40. Где я пошел 0 lt;= i lt;n, регистр r40 перейдет в 0 lt;= r40 lt;8 * n * n.
0010 ; for (i = 0, i lt; n; i++) 0020 { 0030 ; r1 = #0 ; i = 0, becomes dead code 0050 load r2, n 0220 fr3 = #0.0 0340 fr4 = #1.0 0055 ; r8 = #0 killed ; r8 no longer used 0056 r40 = #0 ; initial value for r8 * 8 0057 r30 = r2 * #8 ; increment for r40 0058 ; r20 = r2 killed ; r8 = 0, becomes dead code 0058 r22 = r2 * #8 ; r20 = r2 005A ; r14 = #0 killed ; r8 = 0, becomes dead code 005B r15 = #0 ; r14 = 0 005C r49 = r2 + #1 005D r50 = r49 * #8 ; strength reduced increment 005D r60 = r2 * r30 ; new limit for r40 0040 G0000: 0060 ; cmp r1, r2 killed ; i lt; n; induction variable replaced 0065 cmp r40, r60 ; i * 8 * n lt; 8 * n * n 0070 bge G0001 0080 0118 r10 = r40 ; strength reduced expression to r40 0090 ; for (j = 0; j lt; n; j++) 0100 { 0120 G0002: 0147 cmp r10, r22 ; r10 = 8*(r8 + j) lt; 8*(r8 + n) = r22 0150 bge G0003 0160 0170 ; A[i,j] = 0.0; 0230 fstore fr3, A[r10] 0240 0245 r10 = r10 + #8 ; strength reduced multiply 0260 br G0002 0270 } 0280 G0003: 0290 ; A[i,i] = 1.0; 0350 fstore fr4, A[r15] 0360 0370 ;i++ 0380 ; r1 = r1 + #1 killed ; dead code (r40 controls loop) 0385 ; r8 = r8 + r2 killed ; dead code 0386 r40 = r40 + r30 ; strength reduce expression r8 * 8 0388 r22 = r22 + r30 ; strength reduce r22 = r20 * 8 0389 r15 = r15 + r50 ; strength reduce r15 = r14 * 8 0390 br G0000 0400 } 0410 G0001:
Снижение силы оператора использует математические тождества для замены медленных математических операций более быстрыми операциями. Преимущества зависят от целевого ЦП, а иногда и от окружающего кода (который может повлиять на доступность других функциональных блоков в ЦП).
Исходный расчет | Расчет замены |
---|---|
у = х / 8 | у = х gt;gt; 3 |
у = х * 64 | у = х lt;lt; 6 |
у = х * 2 | у = х lt;lt; 1 |
у = х * 15 | у = (х lt;lt; 4) - х |
y = x / 10 (где x имеет тип uint16_t) | y = ((uint32_t) x * (uint32_t) 0xCCCD) gt;gt; 19) |
y = x / π (где x имеет тип uint16_t) | y = (((uint32_t) x * (uint32_t) 0x45F3) gt;gt; 16) + x) gt;gt; 2) |
Индукционная переменная или рекурсивное уменьшение силы заменяет функцию некоторой систематически изменяющейся переменной более простым вычислением с использованием предыдущих значений функции. В процедурном языке программирования это применимо к выражению, включающему переменную цикла, а в декларативном языке - к аргументу рекурсивной функции. Например,
f x =... (3 ** x)... (f (x + 1))...
становится
f x = f' x 1 where f' x z =... z... (f' (x + 1) (3 * z))...
Здесь модифицированная рекурсивная функция f ' принимает второй параметр z = 3 ** x, позволяя заменить дорогостоящие вычисления (3 ** x) более дешевыми (3 * z).
Снижение силы.
-3 / 2
оценивается-1
как, тогда как-3 gt;gt; 1
оцениваетсякак-2
. Таким образом, в этом случае компилятор не может оптимизировать деление на два, заменив его битовым сдвигом.Эта статья основана на материалах, взятых из Free On-line Dictionary of Computing до 1 ноября 2008 г. и включенных в соответствии с условиями «перелицензирования» GFDL версии 1.3 или новее.