1. ホーム
  2. c++

[解決済み] なぜラムダはプレーンな関数よりもコンパイラによって最適化できるのですか?

2022-04-29 10:14:08

質問

著書の中で The C++ Standard Library (Second Edition) Nicolai Josuttisは、ラムダはプレーンな関数よりもコンパイラによって最適化されることがあると述べています。

<ブロッククオート

さらに、C++コンパイラは、ラムダを最適化します。 通常の関数 (213ページ)

それはなぜか?

インライン化に関しては、もう差はないはずだと思ったのですが。ただ、コンパイラーはラムダなどのローカルコンテキストをよりよく理解し、より多くの仮定を立て、より多くの最適化を実行することができるのでしょう。

どのように解決するのですか?

その理由は、ラムダは 関数オブジェクト そのため、関数テンプレートに渡すと、そのオブジェクト専用の新しい関数がインスタンス化されます。そのため、コンパイラはラムダ呼び出しをインライン化することができる。

一方、関数の場合は、昔からある注意事項が適用されます。 ポインタ 関数テンプレートに渡されるため、コンパイラは従来から関数ポインタ経由の呼び出しをインライン化することに多くの問題を抱えていました。そのため できる 理論的にはインライン化できますが、それは周囲の関数もインライン化した場合のみです。

例として、次のような関数テンプレートを考えてみましょう。

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);
}

... コンパイラは _some_lambda_type::operator () で、その呼び出しをインライン化することができます。(そして,関数 map 任意の の新しいインスタンスを作成します。 map 各ラムダは個別の型を持っているからである)。

しかし、関数ポインタで呼び出すと、次のようにインスタンス化されます。

template <>
void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

... そして、ここ f を呼び出すたびに、異なるアドレスを指します。 map の呼び出しをインライン化することができません。 f を呼び出さない限り、その周囲の map もインライン化されているので、コンパイラは f を特定の1つの関数に変換します。