Mercurial > hg > Papers > 2016 > kaito-master
view paper/chapter5.tex @ 17:3afb4bfe1100
fix
author | Kaito Tokumori <e105711@ie.u-ryukyu.ac.jp> |
---|---|
date | Mon, 15 Feb 2016 07:30:44 +0900 |
parents | a077276f53cc |
children |
line wrap: on
line source
\chapter{評価・考察} 今回の研究で改良を行ったた LLVM/clang 上での CbC コンパイラの評価を試みる. 評価は, コンパイルして出力されたアセンブリコードの確認, 改良前の LLVM, 改良後の LLVM, Micro-C, GCC でコンパイルして得られたプログラムによる実行速度の比較, C で実装した類似のプログラムとの速度比較により行う. \section{アセンブリコードの評価} 以下のリスト \ref{evalCbC},\ref{evalAsmB}, \ref{evalAsmA} はそれぞれコンパイル前の CbC の code segment とコンパイル後のアセンブリコードを示している. リスト \ref{evalAsmB} は omit leaf frame pointer 強制前のアセンブリコード, リスト \ref{evalAsmA} は omit leaf frame pointer 強制後のアセンブリコードである. リスト \ref{evalAsmB} には push, pop 命令によるフレームポインタの操作が入っているのに対し, リスト \ref{evalAsmA} にはその操作がない. このことから, omit leaf frame pointer が正しく動作していることがわかる. \begin{lstlisting}[frame=lrbt,label=evalCbC,caption={コンパイル前}] __code f(int i,stack sp) { int k,j; k = 3+i; goto f_g0(i,k,sp); } \end{lstlisting} \begin{lstlisting}[frame=lrbt,label=evalAsmB,caption={omit leaf frame pointer 強制前}] _f: ## @f .cfi_startproc ## BB#0: ## %entry pushq %rbp Ltmp9: .cfi_def_cfa_offset 16 Ltmp10: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp11: .cfi_def_cfa_register %rbp movl %edi, %eax addl $3, %eax movq %rsi, -8(%rbp) ## 8-byte Spill movl %eax, %esi movq -8(%rbp), %rdx ## 8-byte Reload popq %rbp jmp _f_g0 ## TAILCALL .cfi_endproc \end{lstlisting} \begin{lstlisting}[frame=lrbt,label=evalAsmA,caption={omit leaf frame pointer 強制後}] _f: ## @f .cfi_startproc ## BB#0: ## %entry subq $24, %rsp Ltmp9: .cfi_def_cfa_offset 32 movl %edi, %eax addl $3, %eax movq %rsi, 16(%rsp) ## 8-byte Spill movl %eax, %esi movq 16(%rsp), %rdx ## 8-byte Reload addq $24, %rsp jmp _f_g0 ## TAILCALL .cfi_endproc \end{lstlisting} \section{実行速度の評価} 今回測定に使用したプログラムは環境付き継続と計算を繰り返すプログラムである(リスト \ref{gotowithenv}). 測定は x86-84 アーキテクチャの OS X 上で行った. 表 \ref{result} が測定結果である. \begin{lstlisting}[frame=lrbt,label=gotowithenv,caption={環境付き継続を繰り返すプログラム}] #define LOOP 50000000 #include <stdio.h> __code print(int n,int result,int orig,__code(*print)(),__code (*exit1)(int, void*),void*exit1env); __code factorial(int n,int result,int orig,__code(*print)(int,int,int,__code(*)(),__code(*)(),void*),__code(*exit1)(int,void *), void *exit1env) { if (n<0) { printf("#0008:err %d!\n",n); goto (*exit1)(0,exit1env); } if (n==0) goto (*print)(n,result,orig,print,exit1,exit1env); else { result += n; n--; goto factorial(n,result,orig,print,exit1,exit1env); } } int calc(int n){ __code (*ret)(int,void *) = __return; void* env = __environment; goto factorial(n,1,n,print,ret,env); return 0; } int main( int ac, char *av[]) { int i; long ans; for(i=LOOP,ans=0;i>0;i--){ ans += calc(10); } printf("%ld\n",ans); } __code print(int n,int result,int orig,__code(*print)(),__code (*exit1)(int, void*),void*exit1env) { goto (*exit1)(result,exit1env); } \end{lstlisting} \begin{table}[htpb] \centering \begin{tabular}{|l|r|} \hline コンパイラ名 & 実行速度 (s) \\ \hline LLVM/clang & 3.35 \\ \hline LLVM/clang -O2 & 1.30 \\ \hline LLVM/clang (old) & 23.30 \\ \hline Micro-C & 1.29 \\ \hline GCC & 14.73 \\ \hline GCC -O2 & 12.96 \\ \hline \end{tabular} \caption{Mac OS X での Micro-C, GCC, LLVM/clang の環境付き継続の速度比較} \label{result} \end{table} 結果から、改良前の LLVM/clang とくらべて現在のものが最適化込みで 10 倍以上, 最適化を除いても 7 倍近い速度が出ていることがわかる. このことから通常の setjmp, longjmp の処理の重さが伺える. また, GCC のコンパイラの速度を大きく上回っていることから, nested function を用いた実装よりもこちらのほうが速いということがわかる. Micro-C コンパイラは直接アセンブラを生成しているので非常に高速であるが, 最適化を利用することで同等の速度が実現できている. \section{C, Scheme との比較} CbC と他の言語との比較を行う. 比較対象は C, 継続を用いることのできる Scheme とした. Scheme のコンパイラには Chicken を使用した. 比較に使用したプログラムは C の関数呼び出し, CbC の軽量継続, Scheme の継続 を繰り返し行うものである (リスト \ref{calc}, \ref{calcCbC}, \ref{calcScheme}). 結果は以下のようになった. \begin{lstlisting}[frame=lrbt,label=calc,caption={Cの計測用コード}] #define LOOP 500000000 #include <stdio.h> #include <stdlib.h> int func4(int a, int b){ return a+b; } int func3(int a, int b){ return func4(b,b/a); } int func2(int a, int b){ return func3(b,a*b); } int func1(int a, int b){ return func2(b,a+b); } int start_func(int loop){ int i, a; a = 0; for (i=0;i<loop;i++) a += func1(1,2); return a; } int main( int ac, char *av[]){ printf("%d\n",start_func(LOOP)); return 0; } \end{lstlisting} \begin{lstlisting}[frame=lrbt,label=calcCbC,caption={CbCの計測用コード}] #define LOOP 500000000 #include <stdio.h> #include <stdlib.h> __code code4(int a, int b, int loop, int ans){ goto start_code(ans+a+b, loop-1); } __code code3(int a, int b, int loop, int ans){ goto code4(b,b/a,loop, ans); } __code code2(int a, int b, int loop, int ans){ goto code3(b,a*b,loop, ans); } __code code1(int a, int b, int loop, int ans){ goto code2(b,a+b,loop, ans); } __code start_code(int ans, int loop){ if (loop>0) goto code1(1,2,loop, ans); else goto print(ans); } int main( int ac, char *av[]){ goto start_code(0,LOOP); return 0; } __code print(int a){ printf("%d\n",a); exit(0); } \end{lstlisting} \begin{lstlisting}[frame=lrbt,label=calcScheme,caption={Schemeの測定用コード}] (define LOOP 500000000) (define (print_ans ans) (print ans)) (define (code4 a b loop ans) (start_code (+ ans (+ a b)) (- loop 1))) (define (code3 a b loop ans) (code4 b (/ b a) loop ans)) (define (code2 a b loop ans) (code3 b (* a b) loop ans)) (define (code1 a b loop ans) (code2 b (+ a b) loop ans)) (define (start_code ans loop) (if (> loop 0) (code1 1 2 loop ans) (print_ans ans) )) (start_code 0 LOOP) \end{lstlisting} \begin{table}[htpb] \centering \begin{tabular}{|l|r|} \hline 言語 & 実行速度 (s) \\ \hline C & 4.85 \\ \hline CbC & 3.10 \\ \hline Scheme & 39.24 \\ \hline \end{tabular} \caption{Mac OS X での C, CbC, Scheme の実行速度比較} \label{comp} \end{table} 結果より, CbC の軽量継続が関数呼び出しよりも高速であることがわかる. このことからも関数呼び出し, return の際に行われるスタック操作の処理の重さがわかるだろう.