最適化12
以前書いた、以下のようなパターンですが、
public class Test { public static void main(String[] args) { int a = 0; a += 10; a += 20; System.out.println(a); } }
これをコンパイルして、逆アセンブルします。ちなみに上のはjavaです。
C:\>javap -c Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iinc 1, 10
5: iinc 1, 20
8: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_1
12: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
15: return
}
iincが2回あることに注目してください。
10足して、また20足すっていう処理を、一度に30足すっていう風には最適化してくれないみたいですね。なぜなんでしょうね?たぶん、何か理由があるんだとは思うんですが。
なんでこんなことを考えているかというと、トランスレータが生成したコード中に、
ひとつのブロック中で同じ変数に複数回の演算を行っている部分が多数出てくるからです。
実際にトランスレータが生成したコード(C#)は、以下のような感じです。
switch (pc) { case 0xC002: clk -= 4; a = ppu.StatusRegister; flgN = (a & 0x80) != 0; pc += 3; clk -= 2; if (!flgN) { clk--; pc = 0xC002; if (clk > 0) { goto case 0xC002; } else break; } pc += 2; clk -= 2; x = 0; pc += 2; break; case 0xC01D: clk -= 5; StMem(LdWramW(0) + y, a); pc += 2; clk -= 2; y = (y - 1) & 0xff; flgZ = (y == 0); pc += 1; clk -= 2; if (!flgZ) { clk--; pc = 0xC01D; if (clk > 0) { goto case 0xC01D; } else break; } pc += 2; clk -= 5; m = (LdWramB(1) - 1) & 0xff; StWramB(1, m); flgN = (m & 0x80) != 0; pc += 2; break; case 0xC04F: clk -= 2; a = 0; pc += 2; clk -= 3; StWramB(32, a); pc += 2; clk -= 3; a = LdWramB(48); flgZ = (a == 0); pc += 2; clk -= 2; if (flgZ) { clk--; pc = 0xC05D; if (clk > 0) { goto case 0xC05D; } else break; } pc += 2; clk -= 3; a = LdWramB(80); pc += 2; clk -= 2; t = a - 1; flgZ = (t & 0xff) == 0; pc += 2; clk -= 2; if (!flgZ) { clk--; pc = 0xC060; if (clk > 0) { goto case 0xC060; } else break; } pc += 2; goto case 0xC05D; // 以下略
ここでは、見やすいように整形してます。
clkとかpcに対する加算や減算が、かなりたくさん出てくるのがわかると思います。
clkは残り実行クロック数の変数、pcはプログラムカウンタの変数です。
実際、トランスレータが生成したコード中で、一番出現回数が多い変数はclkでした。
これらの演算を、コンパイラが最適化してまとめてくれるなら、結構違うと思うんですが…
この最適化を自前でやるとなると、結構面倒ですねえ。