最終更新日時:
が更新

履歴 編集

7. Practical considerations

7.1. Performance

標準 STL の関数オブジェクトやバインダのオーバーヘッドと同様に理論的には STL アルゴリズム や λファンクタの使用による手書のループと比較した全てのオーバーヘッドは最適化により解消される。 コンパイラに依存しているものの、これは実際に正しい。 GCC 3.0.4 を使用して、Intel Pentium 4 の 1.5 GHz のマシン上で二つのテストを行なった。 最適化フラグは -O3 とした。

最初のテストでは、λファンクタと明示的に書いた関数オブジェクトを比較した。 繰り返し自分自身の値をかける単項関数を両方の形で定義した。 恒等関数から始めて、x^5 まで行なった。 この式は std::transform のループの内部で呼出され、ある std::vector<int> から引数を読み出し、結果を別な std::vector<int> に格納した。 vector の要素数は 100 とした。 実行時間は、 Table 3 に示してある。 二つの方法に大きな差はないことが分かる。

二つ目のテストでは、100個の要素の vector の各要素に対して操作を行うため、再び std::transform を使用した。 今回は、vector の要素の型は double であり、非常に簡単な算術式から始めて、少しずつ複雑なものへとしていった。 実行時間を Table 4 に示す。

ここでは、従来の STL スタイルの無名関数もテストに加えている。 これらの式はより複雑になるため提示しない。 例えば、 Table 4 の最後の式は、従来の STL の方法で記述すると、七回の compose2 の呼出しと、八回の bind1st の呼出し、 multipliesminusplus のオブジェクトの構築に合計十四回のコンストラクタの呼出しを含むことになる。 このテストにおいては、対応する手書きの関数オブジェクトと比較して、BLL の式は少し遅い。 (大まかに言って平均で 10%、最大で 14% ほど) 従来の STL の式ではパフォーマンスの低下はもう少し大きい。 一番単純な式では 27% ほどになっている。

これらのテストから、BLL は STL の関数オブジェクトと比較してパフォーマンスの低下を招いてはいない。 適当な最適化コンパイラを使用すれば、パフォーマンスの特性は、従来の STL を使ったものに匹敵すると思われる。 さらに、単純な式であれば、明示的に記述した関数オブジェクトの場合のパフォーマンスに近いと考えられる。 しかし、λファンクタの評価は、インライン宣言された小さな関数の呼出しの連続であるため、コンパイラがそれらのインライン展開に失敗すると、パフォーマンスは低下するだろう。 こうなった場合、実行時間は二倍以上になるかもしれない。 上記のテストはこのようなことが発生する式を含んでいないが、一見すると単純な式で、このようなことになったこともある。

Table 3.

Test 1. λ式で記述した整数の乗算と従来の手書きの関数オブジェクトクラスで記述した整数の乗算のCPU 時間の比較。 実行時間は任意の単位で表現している。

expression lambda expression hand-coded function object
x 240 230
x*x 340 350
x*x*x 770 760
x*x*x*x 1180 1210
x*x*x*x*x 1950 1910

Table 4.

Test 2. λ式で記述した算術式と、 compose2bind1st などを使用した従来の STL の無名関数で記述した算術式と、従来の手書きの関数オブジェクトクラスで記述した算術式のCPU 時間の比較。 BLL の言葉を使用すれば、 ab は式の束縛変数であり、x は自由変数である。 全ての変数は double 型である。 実行時間は任意の単位で表現している。

expression lambda expression classic STL expression hand-coded function object
ax 330 370 290
-ax 350 370 310
ax-(a+x) 470 500 420
(ax-(a+x))(a+x) 620 670 600
((ax) - (a+x))(bx - (b+x))(ax - (b+x))(bx - (a+x)) 1660 1660 1460

初期のライブラリを使用した更なるパフォーマンスのテストは、 [Jar00] に記述してある。

7.2. About compiling

BLL は同じテンプレートの数多くの再帰的なインスタンス化を行うため、テンプレートを非常に大量に使用している。 このことは(少なくとも)次の三つのことを意味している。

  • 非常に複雑なλ式を書くことができても、恐らくそれはいい考えではない。 そのようなλ式のコンパイルにはたくさんのメモリを必要とし、コンパイルが遅くなってしまうかもしれない。
  • 例え単純なλ式から生成されたとしても、λファンクタの型は不可解である。 通常プログラマはλファンクタそのものの型を扱う必要は全くない。 しかし、λ式中でエラーが発生した場合は、通常コンパイラは関係するλファンクタの型を出力する。 特に、コンパイラが一連のテンプレートのインスタンス化の全てを出力すると、エラーメッセージは非常に長くなり、意味を理解することが難しくなる。
  • C++ 標準では、無限再帰の検出のために、テンプレートのネストは 17 階層までと提案されている。 複雑なλテンプレートは簡単にこの制限を越えてしまう。 多くのコンパイラはより深いテンプレートのネストを許しているが、一般的には、明示的に制限を上げるコマンドライン引数を必要とする。

7.3. Portability

BLL は以下のコンパイラで動作する。 つまり、これらのコンパイラは BLL を含んだテストケースをコンパイルすることができる。

  • GCC 3.0.4
  • KCC 4.0f with EDG 2.43.1
  • GCC 2.96 (exception_test.cpp のみコンパイラの内部エラーにより失敗する)

7.3.1. Test coverage

以下のリストは含まれているテストファイルと、それぞれのファイルが対象とする特性を示している。

  • bind_tests_simple.cpp : 異なる引数の数と、対象の関数の異なる形態(関数ポインタ、関数オブジェクト、メンバ関数)に対する bind 式。 bind 式による関数の合成。
  • bind_tests_simple_function_references.cpp : 対象の関数を関数ポインタの代りに、関数への参照を使用して bind_tests_simple.cpp の全てのテストを繰り返す。
  • bind_tests_advanced.cpp : ネストした bind 式、unlambdaprotectconst_parametersbreak_const のテストを含む。 λファンクタを実引数として他のλファンクタに渡し、カリー化し、関数オブジェクトの返り値の型を指定するために、テンプレート sig を使用するテスト
  • operator_tests_simple.cpp : 単項、二項の算術演算、ビット演算、比較、論理演算、インクリメント、デクリメント、合成、代入、 subscrict アドレス演算、参照はがし、コンマ演算子というλ式のためにオーバーロードされるすべての演算子を使うテスト。 加算演算子、や減算演算子を使ったポインタの算術演算と同様にシフト演算子の本質を流す。
  • member_pointer_test.cpp : メンバの演算子へのポインタは非常に複雑なので、テストファイルを別にした。
  • control_structures.cpp : ループと if 文の構造のテスト。
  • switch_construct.cpp : 全てのサポートする switch 文 の引数の数と default の場合があるかどうかのテストを含む。
  • exception_test.cpp : catch ブロックの数を変化させながら、try/catch のための例外を送出するテストを含む。
  • constructor_tests.cpp : constructordestructornew_ptrdelete_ptrnew_arraydelete_array に対するテストを含む。
  • cast_test.cpp : 四つのキャスト式のテスト。 typeidsizeof のテストも含む。
  • extending_return_type_traits.cpp : ユーザ定義型のための返り値の型推論システムの拡張のテスト。 いくつかのユーザ定義の演算子と、それに対応する特殊化のための返り値の型推論テンプレートを含む。
  • is_instance_of_test.cpp : 内部的に使用される特性テンプレートのためのテストを含む。 この特性テンプレートは、与えられた型があるテンプレートのインスタンスかどうかを検出する。
  • bll_and_function.cpp : λファンクタと boost::function を一緒にしようするテストを含む。