Expression Template(式テンプレート)の効果

id:Cryolite:20040507#p1より続きます.
というわけで,今回はETの効果について検証します.今回検証に用いるのは以下の高次元のvectorの和の計算についての速度です.
t = u + v + w;
これを,ETによって和を実装したvectorクラスet_vector.hppを用いて実装してみます.
一方で,比較対象としてETを用いない単純なvectorクラスsimple_vector.hppid:Cryolite:20040506において書いたもの)を用いた実装と比較します.
単純なvectorクラスは,色々比較するために以下の3つの使い方で実装してみます.
一つ目は単純にoperator+を使ったもの(Simple)

for(size_t i = 0; i < n; ++i){
  t = u + v + w;
}

二つ目は代入演算子を使ったもの(Assignment)

t = u;
t += v;
t += w;

三つ目は各要素に対して陽にloopを使うもの(Explicit)

for(size_t i = 0; i < n; ++i){
  t[i] = u[i] + v[i] + w[i];
}

実験に用いたソースコードet_vector.hpp, simple_vector.hpp, et_test.cppです.簡単なことにしか使って無いですが,一応boostを必要としています.
明らかにしておくべき実験環境は以下です.

  • CPU: Athlon 1.4GHz 2次キャッシュサイズが分からないのですが恐らく256KBと思われます
  • Memory: 3.0GB PC100・・・多分(をいをい
  • OS: RedHat Linux 9
  • Compiler: Intel C++ Compiler 7.1
  • Compiler switch: -O3 -i_dynamic

これで,vectorの次元数を変えながら,同じ計算を繰り返して速度を見ます.
以下が結果です.単位は秒.

次元数*繰り返し回数SimpleAssignmentExplicitET
10*100000008.471.540.730.79
100*10000002.571.090.740.61
1000*1000001.861.040.730.58
10000*1000013.123.682.352.42
100000*100015.707.854.154.23
1000000*10016.018.054.684.67
10000000*1015.427.824.174.23
結果を検証します.
まず,分かるのはETが陽にloopを書いたコードと同等の性能を示していることです.
一方,Simpleは一時オブジェクトを生成し(そのためにヒープの確保も要求し),要素のコピーを行うので遅いということが結果に明らかに出ています.
自分がちょっと想定外だったのがAssignmentがExplicitやETに若干劣っている点で,これは代入先のtの要素を時間間隔を置いて複数回参照するからでしょう.見落としていました.
後,もう一つ分かるのは全て計算量としては同じ実験のはずなのに,vectorの次元が10000次元を超えるぐらいから全体的にがたんと遅くなっている点で,これは演算に使うvector全部がキャッシュに乗り切らなくなっているためでしょう.
ということで,ETは直感的なコードを維持したまま効率的な実装を提供できるということができます.
で,次にGCCコンパイルしたコードでの実験結果を見ます.実験条件はコンパイラ以外同じです.

  • Compiler: GCC 3.2.2
  • Compiler switch: -O3

結果です.

次元数*繰り返し回数SimpleAssignmentExplicitET
10*100000009.511.980.931.77
100*10000003.281.580.881.60
1000*1000002.711.540.801.60
10000*1000013.363.762.682.78
100000*100015.557.814.134.23
1000000*10015.817.974.674.68
10000000*1015.407.774.144.23
全体的にICCのと比較すれば,さすがにICCは良いコンパイラだなと思わせますが,これを載せた意図はそこではないです.
基本的にはICCと同じ傾向の結果ですが,一つだけ次元が小さいときのETの結果が思わしくありません.
ETは,説明したように,各演算で小さな式オブジェクトを生成します.それらは最適化されれば消去してしまうことができるオブジェクトですが,このような積極的な最適化を行ってくれるC++コンパイラはまだまだ少ないです.そして,そのような最適化が行われないコンパイラでは式オブジェクト生成のコストがかかってしまう場合があります.このGCCの結果も恐らくその影響かと思われます.
なお,次元が大きい領域ではそのようなoverheadがメモリアクセスのoverheadに償却されて,Explicitと同等の性能をみせています.
いずれにせよ,Simpleに比べれば,ETは非常に良い性能を発揮することが分かります.
正直,こんな簡単な実装でここまでの結果が出るとは思っていませんでした.
というわけで,次回はETの負の面について少々議論します.
id:Cryolite:20040513#p1へ続きます.