ラムダのコンパイラ最適化とプレーン関数
Nicolai Josuttis は、著書「The C Standard Library (Second Edition)」の中で次のように主張しています。ラムダは単純な関数よりもコンパイラによって最適化できるということです。ラムダ関数とプレーン関数の両方をインライン化できることを考えると、これは直観に反するように思えるかもしれません。ただし、この 2 つの間には、ラムダの場合により良い最適化を可能にする微妙な違いがあります。
違い: 関数オブジェクトと関数ポインター
ラムダは次のとおりです。関数オブジェクトは、単純な関数は本質的に関数ポインタです。ラムダを関数テンプレートに渡すと、そのオブジェクト専用の新しい関数がインスタンス化されます。これにより、コンパイラーはラムダ呼び出しを簡単にインライン化できます。
対照的に、プレーン関数を関数テンプレートに渡すと、関数ポインターが渡されます。コンパイラは歴史的に、関数ポインタを介した呼び出しのインライン化に苦労してきました。理論的にはインライン化できますが、周囲の関数もインライン化されている場合にのみ発生します。
例
次の関数テンプレートを考えてみましょう:
template <typename Iter, typename F> void map(Iter begin, Iter end, F f) { for (; begin != end; ++begin) *begin = f(*begin); }
ラムダで呼び出すと:
int a[] = { 1, 2, 3, 4 }; map(begin(a), end(a), [](int n) { return n * 2; });
一意のインスタンス化が行われます:
template <> void map<int*, _some_lambda_type>(int* begin, int* end, _some_lambda_type f) { for (; begin != end; ++begin) *begin = f.operator()(*begin); }
コンパイラはラムダの Operator() を識別し、それに対する簡単なインライン呼び出しを行うことができます。
しかし、関数ポインタを使用して呼び出された場合:
map(begin(a), end(a), &multiply_by_two);
インスタンス化は次のようになります:
template <> void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) { for (; begin != end; ++begin) *begin = f(*begin); }
ここで、f はマップが呼び出されるたびに異なる関数を参照するため、マップが呼び出されるたびに異なる関数が参照されます。
結論
関数オブジェクトとしての独自のタイプのラムダにより、コンパイラは特定の関数のインスタンス化を作成し、その呼び出しをシームレスにインライン化できます。この強化された最適化機能により、ラムダは単純な関数と区別され、コードのパフォーマンスと効率を向上させるための好ましい選択肢となります。
以上がラムダはコンパイラによって単純な関数よりも最適化できますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。