関数呼び出し速度測定トーナメント

様々な方法で関数を呼び出し、その速度を競います。

注意点

厳密に平等になるようにはしてないです。
単純にその呼び出しを使う場合を想定したプログラムにしてあります。
自分が使いたい関数呼び出し+αを今回使います。

PCスペッ子

以下の環境で行います。

  • XPSP3
  • Core2 Quad CPU 2.83GHz
  • 3.00GB RAM
  • VS2010 Express
  • ニコ動再生

測定について

2種類測定します

・デフォのRelease
・デフォのReleaseの最適化なし

測定にはこれを使います
QueryPerformanceFrequency()
QueryPerformanceCounter()

参考URL:
http://msdn.microsoft.com/ja-jp/library/cc410968.aspx
んで、次のようにして時間とってます。

double time = double( (end.QuadPart - begin.QuadPart)*1000.0 / fre.QuadPart );

処理内容

宣言はループ外で行い。ループ内で以下の関数を呼んでもらいます。
最適化で関数呼び出しが無視されないように結果値は保持し、
ループ毎のタイム結果表示タイミングで使用します。

inline int foo(int i, int j){
	return i+j;
}

を1024回呼んでもらいます。
そしてそれを1<<17(131072)繰り返します。*1

参加メンバー

仮想関数

仮想関数「私についてくれば問題ない」

class UseVirtualBase
{
public:
	int ret;
	UseVirtualBase():ret(0){}
	virtual void func(int i){
		i = foo(i,i+1);
		ret = i;
	}
};
仮想関数継承

仮想関数継承「以前の私とは違うのだよ!」

class UseVirtual : public UseVirtualBase
{
public:
	void func(int i){
		i = foo(i,i+1);
		ret = i;
	}
};
純粋仮想関数

純粋仮想関数「私は...始まりの関数...」

class UsePureVirtualBase
{
public:
	virtual void func(int i) = 0;
};
class UsePureVirtual
{
public:
	int ret;
	UsePureVirtual():ret(0){}
	void func(int i){
		i = foo(i,i+1);
		ret = i;
	}
};
関数オブジェクト(ファンクタ)

ファンクタ「こいよ。乗りこなしてやるよ。」

template<class T>
class UseFunctor
{
	T m_f;
public:
	int ret;
	UseFunctor():ret(0){}
	void func(int i){
		ret = m_f(i);
	}
};
class Functor
{
public:
	int operator()(int i){
		i = foo(i,i+1);
		return i;
	}
};
関数ポインタ

関数ポインタ「カーネルが俺に走れと言っている」

inline int _func(int i){
	i = foo(i,i+1);
	return i;
}
typedef int(*FUNC)(int i);
class UseFunc
{
public:
	int ret;
	FUNC m_func;
	UseFunc(FUNC f):ret(0){m_func=f;}
	void func( int i ){
		ret = m_func(i);
	}
};
メンバ関数ポインタ

メンバ関数ポインタ「焼きそばパン買う速度なら誰にも負けない!」

class UseMethod
{
	typedef int(UseMethod::*UseMethodFunc)(int i);
	UseMethodFunc m_f;
public:
	int ret;
	UseMethod():m_f(&UseMethod::fff),ret(0){}
	void func(int i){
		UseMethod* p=this;
		ret = (p->*m_f)(i);
	}
private:
	int fff(int i){
		i = foo(i,i+1);
		return i;
	}
};

結果

名前 デフォのReleaseの最適化なし デフォのRelease
仮想関数 0.008649 0.000979
仮想関数継承 0.008581 0.000983
純粋仮想関数 0.008574 0.000989
ファンクタ 0.011816 0.000976
関数ポインタ 0.010986 0.002889
メンバ関数ポインタ 0.011731 0.003166

まとめ

まぁ大方予想通り。
・関数ポインタは最適化されにくい。
・ファンクタは最適化されやすい。(どっかの記事でも書いてあったこと)
・仮想周りは大して変わらない。
・仮想とファンクタはほとんど変わらないけど、何度試しても上下関係は変わらないので、ほんのちょっとファンクタが速い。

数値だけ見れば差はあるけど、呼び出し回数が通常じゃありえないので。
よほどクリティカルな場所じゃないかぎり、好きに使っても体感速度は何も変わらないかと。
それよりもどうでもいい場所で速度重視してフレキシブルを失うとストレスで老化速度が上昇する。

最後に

ニコ動のMUGENトーナメント見ながらやってたので、テンション影響されまくってる。
台詞とかテンション上がってやった、結構後悔している。
頭爆発したら消す。

ソース抜粋

一応コア部分となるソース。

inline int foo(int i, int j){
	return i+j;
}


class UseVirtualBase
{
public:
	int ret;
	UseVirtualBase():ret(0){}
	virtual void func(int i){
		i = foo(i,i+1);
		ret = i;
	}
};
class UseVirtual : public UseVirtualBase
{
public:
	void func(int i){
		i = foo(i,i+1);
		ret = i;
	}
};
class UsePureVirtualBase
{
public:
	virtual void func(int i) = 0;
};
class UsePureVirtual
{
public:
	int ret;
	UsePureVirtual():ret(0){}
	void func(int i){
		i = foo(i,i+1);
		ret = i;
	}
};

template<class T>
class UseFunctor
{
	T m_f;
public:
	int ret;
	UseFunctor():ret(0){}
	void func(int i){
		ret = m_f(i);
	}
};
class Functor
{
public:
	int operator()(int i){
		i = foo(i,i+1);
		return i;
	}
};

inline int _func(int i){
	i = foo(i,i+1);
	return i;
}
typedef int(*FUNC)(int i);
class UseFunc
{
public:
	int ret;
	FUNC m_func;
	UseFunc(FUNC f):ret(0){m_func=f;}
	void func( int i ){
		ret = m_func(i);
	}
};
class UseMethod
{
	typedef int(UseMethod::*UseMethodFunc)(int i);
	UseMethodFunc m_f;
public:
	int ret;
	UseMethod():m_f(&UseMethod::fff),ret(0){}
	void func(int i){
		UseMethod* p=this;
		ret = (p->*m_f)(i);
	}
private:
	int fff(int i){
		i = foo(i,i+1);
		return i;
	}
};

void main(void)
{
//	UseVirtualBase t;
//	UseVirtual t;	
//	UsePureVirtual t;
//	UseFunctor<Functor> t;
	UseFunc t(_func);	
//	UseMethod t;		

	double total = 0.0;
	enum{
		CNT=1<<17,
	};
	LARGE_INTEGER fre, begin, end;
	QueryPerformanceFrequency(&fre);

	for(int j=0; j < CNT; ++j){

		QueryPerformanceCounter(&begin);
		DWORD beginTime = timeGetTime();
		for( int i = 0; i < 1024;++i ){
			t.func(i);
		}
		DWORD endTime   = timeGetTime();
		QueryPerformanceCounter(&end);

		double time = double( (end.QuadPart - begin.QuadPart)*1000.0 / fre.QuadPart );

		total += time;
		printf("%d %f\n", t.ret, time );
		
	}
	printf("Avg:%f", total/(double)CNT);
	_getch();
}

*1:17に意味はありません.速度的にちょうど良かったから