「60分でできる!JITコンパイラ自作入門」
(0)
- JITコンパイラはインタプリタを高速にするために使われる定番の技術です。
- たいして難しい技術ではないのですが、多くの人は難しいと漠然と思っているようです。
- だから解説します。
- 以下でも示しますが、普通のインタプリタをJITコンパイラに切り替えるだけで10倍以上高速化できます。
- JITコンパイラ化する以外で10倍も速くするのは相当に困難なので、とてもおすすめの方法なのです。
(1) 64bit-インタプリタ編
- まず、以下のような命令セットを想定して、インタプリタとJITコンパイラを作り比べてみます。
Sub_AI(SP,16); // SP = SP - 16;
Mov_RI(R0,0); // R0 = 0;
Sto_RMD(R0,SP,0); // [SP+0] = R0;
Sto_RMD(R0,SP,8); // [SP+8] = R0;
Lb_I(1); // L_1:
Lod_RMD(R0,SP,8); // R0 = [SP+8];
Lod_RMD(R1,SP,0); // R1 = [SP+0];
Add_RR(R0,R1); // R0 += R1;
Sto_RMD(R0,SP,0); // [SP+0] = R0;
Lod_RMD(R0,SP,8); // R0 = [SP+8];
Add_RI(R0,1); // R0 += 1;
Sto_RMD(R0,SP,8); // [SP+8] = R0;
CmpJlt_RII(R0,100000000,1); // if (R0 < 100000000) goto L_1;
Lod_RMD(R0,SP,0); // R0 = [SP+0];
Add_AI(SP,16); // SP = SP + 16;
End(); // R0に計算結果が入っているので、それを表示して終了;
// int s, i; s = 0;
// for (i = 0; i < 100000000; i++) { s = s + i; }
| Mov_RI(Rx,const); | Rx = const; |
| Lod_RMD(Rx,Ax,disp); | Rx = [Ax + disp]; |
| Sto_RMD(Rx,Ax,disp); | [Ax + disp] = Rx; |
| Add_RR(Rx,Ry); | Rx += Ry; |
| Add_RI(Rx,const); | Rx += const; |
| Add_AI(Ax,const); | Ax += const; |
| Sub_AI(Ax,const); | Ax -= const; |
| Lb_I(x); | L_x: |
| CmpJlt_RII(Rx,const,y); | if (Rx < const) goto L_y; |
| End(); | R0の値を表示して終了 |
- この10個の命令があれば上記は動きます。
- SPはA6レジスタの別名だということにします。
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include <inttypes.h>
struct Code { int64_t op, a, b, c; };
enum { Mov_RI=1, Lod_RMD, Sto_RMD, Add_RR, Add_RI, Add_AI, Sub_AI, Lb_I, CmpJlt_RII, End };
enum { R0=0, R1=1, SP=6 };
int main()
{
static struct Code t[17] = {
{ Sub_AI, SP, 16, -0 }, // -0は命令としては意味を持ってないパラメータ.
{ Mov_RI, R0, 0, -0 },
{ Sto_RMD, R0, SP, 0 },
{ Sto_RMD, R0, SP, 8 },
{ Lb_I, 1, -0, -0 },
{ Lod_RMD, R0, SP, 8 },
{ Lod_RMD, R1, SP, 0 },
{ Add_RR, R0, R1, -0 },
{ Sto_RMD, R0, SP, 0 },
{ Lod_RMD, R0, SP, 8 },
{ Add_RI, R0, 1, -0 },
{ Sto_RMD, R0, SP, 8 },
{ CmpJlt_RII, R0, 100000000, 1 },
{ Lod_RMD, R0, SP, 0 },
{ Add_AI, SP, 16, -0 },
{ End, -0, -0, -0 },
{ 0, -0, -0, -0 }
};
int64_t lb[256], r[8], a[8], pc;
char m[256]; // メモリ.
// Lb_I(x)命令を探して位置をlb[]に格納する.
for (pc = 0; t[pc].op > 0; pc++) {
if (t[pc].op == Lb_I) { lb[t[pc].a] = pc + 1; }
// 高速化のために、ラベル命令の次の命令の位置を登録している.
}
// 実行開始.
pc = 0; a[6] = 256;
clock_t tm0 = clock();
for (;;) {
struct Code *tp = &t[pc]; int64_t *mp;
switch (tp->op) {
case Mov_RI: r[tp->a] = tp->b; pc++; continue;
case Lod_RMD: mp = (int64_t *) (m + a[tp->b] + tp->c); r[tp->a] = *mp; pc++; continue;
case Sto_RMD: mp = (int64_t *) (m + a[tp->b] + tp->c); *mp = r[tp->a]; pc++; continue;
case Add_RR: r[tp->a] += r[tp->b]; pc++; continue;
case Add_RI: r[tp->a] += tp->b; pc++; continue;
case Add_AI: a[tp->a] += tp->b; pc++; continue;
case Sub_AI: a[tp->a] -= tp->b; pc++; continue;
case Lb_I: pc++; continue;
case CmpJlt_RII: if (r[tp->a] < tp->b) { pc = lb[tp->c]; } else { pc++; } continue;
case End: goto fin;
}
}
fin:
printf("R0=%" PRId64 " (%.3f[sec])\n", r[0], (clock() - tm0) / (double) CLOCKS_PER_SEC);
return 0;
}
- 実行すると R0=4999999950000000 と表示されます。
(2) 64bit-JITコンパイラ編