Jump to content

Снижение прочности

При построении компилятора сокращение силы — это оптимизация компилятора , при которой дорогостоящие операции заменяются эквивалентными, но менее затратными операциями. [1] Классический пример уменьшения силы преобразует сильные умножения внутри цикла в более слабые сложения – то, что часто происходит при адресации массивов ( Cooper, Simpson & Vick 1995 , стр. 1) .

Примеры снижения силы включают замену умножения внутри цикла сложением и замену возведения в степень внутри цикла умножением.

Анализ кода

[ редактировать ]

Большая часть времени выполнения программы обычно занимает небольшой участок кода (называемый « горячей точкой »), и этот код часто находится внутри цикла, который выполняется снова и снова.

Компилятор использует методы для идентификации циклов и распознавания характеристик значений регистров внутри этих циклов. Для снижения прочности компилятор интересуется:

  • Инварианты цикла: значения, которые не изменяются внутри тела цикла.
  • Индукционные переменные: значения, которые повторяются каждый раз в цикле.

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

Снижение силы ищет выражения, включающие инвариант цикла и переменную индукции. Некоторые из этих выражений можно упростить. Например, умножение инварианта цикла c и индукционная переменная i

c = 7;
for (i = 0; i < N; i++)
{
    y[i] = c * i;
}

можно заменить последовательными более слабыми добавками

c = 7;
k = 0;
for (i = 0; i < N; i++)
{
    y[i] = k;
    k = k + c;
}

Пример снижения прочности

[ редактировать ]

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

Представьте себе простой цикл, который присваивает массиву единичную матрицу .

for (i = 0; i < n; i++)
{
    for (j = 0; j < n; j++)
    {
        A[i,j] = 0.0;
    }
    A[i,i] = 1.0;
}

Промежуточный код

[ редактировать ]

Компилятор будет рассматривать этот код как

0010  ; for (i = 0, i < n; i++)
0020  ; {
0030      r1 = #0                        ; i = 0
0040  G0000:
0050      load r2, n                     ; i < n
0060      cmp r1, r2
0070      bge G0001
0080
0090      ; for (j = 0; j < n; j++)
0100      ; {
0110           r3   = #0                 ; j = 0
0120  G0002:
0130           load r4, n                ; j < 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 < 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 < 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 < n; j++)
0100        {
0110           r3   = #0                 ; j = 0
0120  G0002:
0140           cmp r3, r2                ; j < 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 < n
0145           cmp r9, r20               ; r8 + j < 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 < 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 < r8 + n = r20
0147           cmp r10, r22              ; r10 = 8*(r8 + j) < 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 < 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 < 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 < n; j++)
0100        {
0120  G0002:
0147           cmp r10, r22              ; r10 = 8*(r8 + j) < 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 < 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 < 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 < n; j++)
0100        {
0120  G0002:
0147           cmp r10, r22              ; r10 = 8*(r8 + j) < 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:

Последнее умножение

[ редактировать ]

В результате в двух циклах остается только одна операция умножения (в 03:30) во внешнем цикле и никаких умножений во внутреннем цикле.

0010  ; for (i = 0, i < 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 < n
0070      bge G0001
0080
0118      r10  = r40                     ; strength reduced expression to r40
0090      ; for (j = 0; j < n; j++)
0100        {
0120  G0002:
0147           cmp r10, r22              ; r10 = 8*(r8 + j) < 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. Последнее умножение цикла в 03:30 можно уменьшить, добавляя (r2+1)*8 каждый раз в цикле.

0010  ; for (i = 0, i < 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 < n
0070      bge G0001
0080
0118      r10  = r40                     ; strength reduced expression to r40
0090      ; for (j = 0; j < n; j++)
0100        {
0120  G0002:
0147           cmp r10, r22              ; r10 = 8*(r8 + j) < 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 <= i < n, регистр r40 идет 0 <= r40 < 8 * n * n.

0010  ; for (i = 0, i < 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 < n; induction variable replaced
0065      cmp r40, r60                   ; i * 8 * n < 8 * n * n
0070      bge G0001
0080
0118      r10  = r40                     ; strength reduced expression to r40
0090      ; for (j = 0; j < n; j++)
0100        {
0120  G0002:
0147           cmp r10, r22              ; r10 = 8*(r8 + j) < 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:

Другие операции по снижению прочности

[ редактировать ]

При сокращении численности операторов используются математические тождества для замены медленных математических операций более быстрыми. Преимущества зависят от целевого ЦП, а иногда и от окружающего кода (который может повлиять на доступность других функциональных блоков внутри ЦП).

  • замена целочисленного деления или умножения на степень 2 арифметическим или логическим сдвигом [2]
  • замена целочисленного умножения на константу комбинацией сдвигов, сложения или вычитания
  • замена целочисленного деления на константу умножением, используя преимущества ограниченного диапазона машинных целых чисел. [3] Этот метод также работает, если делитель не является целым числом, достаточно большим, чем 1, например √2 или π. [4]
Исходный расчет Расчет замены
у+= 1 у++
y%2 != 0 й и 1
у = х * 2 у = х << 1
у = х/2 у = х >> 1
у = х % 2 у = х и 1
у = х * 15 у = (х << 4) - х
у = (uint16_t)x/10 y = ((uint32_t)x * (uint32_t)0xCCCD) >> 19)
y = (uint16_t)x / π y = (((uint32_t)x * (uint32_t)0x45F3) >> 16) + x) >> 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).

См. также

[ редактировать ]

Примечания

[ редактировать ]
  1. ^ Стивен Мучник; Мучник и партнеры (15 августа 1997 г.). Расширенная реализация проекта компилятора . Морган Кауфманн. ISBN  978-1-55860-320-2 . Снижение силы.
  2. ^ В таких языках, как C и Java, целочисленное деление имеет семантику округления к нулю, тогда как битовый сдвиг всегда округляется в меньшую сторону, что требует особого подхода к отрицательным числам. Например, в Java -3 / 2 оценивается как -1, тогда как -3 >> 1 оценивается как -2. Таким образом, в этом случае компилятор не может оптимизировать деление на два, заменяя его битовым сдвигом.
  3. ^ Гранлунд, Торбьёрн; Питер Л. Монтгомери. «Деление на инвариантные целые числа с использованием умножения» (PDF) .
  4. ^ Джонс, Найджел. «Деление целых чисел на константы» . Архивировано из оригинала 26 марта 2024 года.
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: c2c8eb45d57bb8cea38630af0541c18d__1716431340
URL1:https://arc.ask3.ru/arc/aa/c2/8d/c2c8eb45d57bb8cea38630af0541c18d.html
Заголовок, (Title) документа по адресу, URL1:
Strength reduction - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)