: Loop Unwinding1: Loop Unrolling

[1][2][3]

利点

編集





[4]



Duff's device


欠点

編集






2

[5]

[6]

静的/手動ループ展開

編集

人手による(または静的な)ループ展開は、プログラマがループを分析し展開することでループのオーバーヘッドを低減させるものである。一方コンパイラが行うループ展開を動的ループ展開と呼ぶ。

C言語での簡単な例

編集

100(delete)for  delete() 
通常のループ ループ展開後
 int x;
 for (x = 0; x < 100; x++)
 {
     delete(x);
 }
 int x; 
 for (x = 0; x < 100; x+=5)
 {
     delete(x);
     delete(x+1);
     delete(x+2);
     delete(x+3);
     delete(x+4);
 }

 100 2051

376100 6Duff's device 

複雑性

編集

使 if退
通常のループ ループ展開後
  for i:=1:8 do
   if i mod 2 = 0 then do_evenstuff(i)
                   else do_oddstuff(i);
   next i;
 do_oddstuff(1); do_evenstuff(2);
 do_oddstuff(3); do_evenstuff(4);
 do_oddstuff(5); do_evenstuff(6);
 do_oddstuff(7); do_evenstuff(8);

しかし、もちろん展開される中身は手続き呼び出しである必要はない。次の例ではインデックス変数の計算が関わっている。

通常のループ ループ展開後
 x(1) = 1;
 For i:=2:9 do
           x(i):=x(i - 1)*i;
           print i,x(i);
           next i;
 x(1):=1;
 x(2):=x(1)*2; print 2,x(2);
 x(3):=x(2)*3; print 3,x(3);
 ...etc.
 .

 print  x(i)  x(i - 1)  x(i)  x
print 2,2;
print 3,6;
print 4,24;
...etc.

 x


WHILEループの展開

編集

WHILEループの擬似コードの例を示す。

通常のループ ループ展開後 展開し「調整」したループ
  WHILE (condition) DO
         action
  ENDWHILE
.
.
.
.
.
.
  WHILE (condition) DO
         action
         IF NOT(condition) THEN GOTO break
         action
         IF NOT(condition) THEN GOTO break
         action
 ENDWHILE
 LABEL break:
.
 IF (condition) THEN
  REPEAT {
         action
         IF NOT(condition) THEN GOTO break
         action
         IF NOT(condition) THEN GOTO break
          action
         } WHILE (condition)
 LABEL break:

展開した例ではENDWHILE(コンパイルされるとループ先頭へのジャンプになる)の実行回数が66%少なくなり、高速化される。

さらに「調整」を加えた擬似コードの例では、最適化コンパイラを使えば無条件ジャンプを全く使わない形に最適化されるだろう。

動的ループ展開

編集

 (JIT) JIT

使System/360Z/ArchitectureFROMTO125650100FROMTO

アセンブリ言語(System/360)の例

編集
* 配列などを指すよう全レジスタを初期化しておく(R14はリターンアドレス)
         LM    R15,R2,INIT                       load R15= '16'、R0= 配列要素数、R1--> FROM配列、R2--> TO配列
LOOP     EQU   *
         SR    R15,R0                            配列要素数を16から引く
         BNP   ALL                               n > 16 なら全命令列を実行し、繰り返す
* MVC命令列の先頭からのオフセットを計算し、展開されたMVCループの所定の位置に無条件ジャンプする
         MH    R15,=AL2(ILEN)                    MVC命令の長さ(この例では6)をかける
         B     ALL(R15)                          インデックス付き分岐命令(その位置からMVC命令を実行)
* 転送テーブル - 1つめが最大オフセットであり、この例では  X'F00'
ALL      MVC   15*256(100,R2),15*256(R1)         * 16番目のエントリの100バイトをFROMからTOに転送
ILEN     EQU   *-ALL                                         MVC命令の長さ。この例では6
         MVC   14*256(100,R2),14*256(R1)         *
         MVC   13*256(100,R2),13*256(R1)         *
         MVC   12*256(100,R2),12*256(R1)         * これら16個の文字転送命令 (MVC) はベース+オフセットのアドレッシングであり
         MVC   11*256(100,R2),11*256(R1)         * オフセットは配列の要素長 (256) ずつ減っている。
         MVC   10*256(100,R2),10*256(R1)         * System/360では命令で指定できるオフセットの最大値は X'FFF' であり
         MVC   09*256(100,R2),09*256(R1)         * それを越えない範囲で展開可能な最大が16要素ということになる。
         MVC   08*256(100,R2),08*256(R1)         * オフセットの大きい方から転送するので、先頭の要素が最後に転送される。
         MVC   07*256(100,R2),07*256(R1)         *
         MVC   06*256(100,R2),06*256(R1)         *
         MVC   05*256(100,R2),05*256(R1)         *
         MVC   04*256(100,R2),04*256(R1)         *
         MVC   03*256(100,R2),03*256(R1)         *
         MVC   02*256(100,R2),02*256(R1)         *
         MVC   01*256(100,R2),01*256(R1)         2番目の要素の100バイトを転送
         MVC   00*256(100,R2),00*256(R1)         1番目の要素の100バイトを転送
*
         S     R0,MAXM1                          残り要素数を引く
         BNPR  R14                               全部転送し終えたので、R14のリターンアドレスで呼び出し元に復帰
         AH    R1,=AL2(16*256)                   FROMへのポインタを1反復ぶんだけずらす
         AH    R2,=AL2(16*256)                   TOへのポインタを1反復ぶんだけずらす
         L     R15,MAXM1                         R15をMVC命令数 (16) に再設定する(計算で壊れているため)
         B     LOOP                              ループの先頭に戻る
*
* ----- 定数と変数の定義(引数として渡すこともできる) ---------------------------------  *
INIT     DS    0A                                LM命令で事前ロードされる4つのアドレス(ポインタ)
MAXM1    DC    A(16)                             MVC命令数
N        DC    A(50)                             配列の要素数(変数であり、変更可能)
         DC    A(FROM)                           配列1の先頭アドレス(ポインタ)
         DC    A(TO)                             配列2の先頭アドレス(ポインタ)
* ----- 配列の定義(動的に確保することも可能) --------------------------------------------------  *
FROM     DS    50CL256                          256バイト×50エントリの配列
TO       DS    50CL256                          256バイト×50エントリの配列

502028956%2108MVC100'XC xx*256+100(156,R1),xx*256+100(R2)' MVC'xx' MVCILEN

45

C言語の例

編集

次の例はC言語で書かれた簡単なプログラムを動的ループ展開したものである。アセンブリ言語の上掲の例とは異なり、変数 (i) が配列の要素を指すのに使われているため、コンパイラはポインタ/インデックスの計算コードを生成することになる。最大限最適化するには、全てのインデックス指定を定数に置き換えなければならない。

#include<stdio.h>

#define TOGETHER (8)

int main(void)
{
 int i = 0; 
 int entries = 50;                                 /* 総処理回数 */
 int repeat;                                       /* ループ回数 */
 int left = 0;                                     /* 余り(後で処理する) */
 
 /* 要素数がBLOCKSIZEで割り切れないとしても、 */
 /* 処理の大部分をwhileループで行うように、反復回数を得る。 */

 repeat = (entries / TOGETHER);                    /* 反復回数 */
 left  =  (entries % TOGETHER);                    /* 余りを計算 */

 /* 8回ぶんの処理を展開 */
 while( repeat-- > 0 )
  {
    printf("process(%d)\n", i    );
    printf("process(%d)\n", i + 1); 
    printf("process(%d)\n", i + 2); 
    printf("process(%d)\n", i + 3); 
    printf("process(%d)\n", i + 4); 
    printf("process(%d)\n", i + 5); 
    printf("process(%d)\n", i + 6); 
    printf("process(%d)\n", i + 7); 

    /* インデックスをまとめて更新 */
    i += TOGETHER; 
  }

 /* switch文を使い、caseラベルのジャンプすることで余りの部分を処理する。 */
 /* フォールスルーにより、余りのぶんを全部処理する。 */
 switch (left)
  {
     case 7 : printf("process(%d)\n", i + 6); 
     case 6 : printf("process(%d)\n", i + 5); 
     case 5 : printf("process(%d)\n", i + 4); 
     case 4 : printf("process(%d)\n", i + 3); 
     case 3 : printf("process(%d)\n", i + 2); 
     case 2 : printf("process(%d)\n", i + 1);      /* 余りが2の場合 */
     case 1 : printf("process(%d)\n", i    );      /* 余りが1の場合 */
     case 0 :                               ;      /* 割り切れた場合 */
  }
}

脚注

編集
  1. ^ Ullman, Jeffrey D.; Aho, Alfred V. (1977). Principles of compiler design. Reading, Mass: Addison-Wesley Pub. Co. pp. 471–2. ISBN 0-201-10073-8 
  2. ^ Petersen, W.P., Arbenz, P. (2004). Introduction to Parallel Computing. Oxford University Press. p. 10 
  3. ^ Nicolau, Alexandru (1985). Loop Quantization: Unwinding for Fine-Grain Parallelism Exploitation. Dept. of Computer Science Technical Report. Ithaca, NY: Cornell University. OCLC 14638257. 
  4. ^ Fog, Agner (2012年2月29日). “Optimizing subroutines in assembly language”. Copenhagen University College of Engineering. pp. 100. 2012年9月22日閲覧。 “12.11 Loop unrolling”
  5. ^ Sarkar, Vivek (2001). “Optimized Unrolling of Nested Loops”. International Journal of Parallel Programming 29 (5): 545–581. doi:10.1023/A:1012246031671. http://www.springerlink.com/content/g36002133451w774/. 
  6. ^ Adam Horvath "Code unwinding - performance is far away"

参考文献

編集
  • Kennedy, Ken; & Allen, Randy. (2001). Optimizing Compilers for Modern Architectures: A Dependence-based Approach. Morgan Kaufmann. ISBN 1-55860-286-0 

関連項目

編集

外部リンク

編集

Chapter 7, pages 8 to 10 at the Wayback Machine (archived 2008-12-01), of Michael Abrash's Graphics Programming Black Book - x86

Generalized Loop Unrolling

Optimizing subroutines in assembly language Agner Fog