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 の際に行われるスタック操作の処理の重さがわかるだろう.