컴파일러가 일반 함수보다 람다를 더 잘 최적화 할 수있는 이유는 무엇입니까?


171

그의 책에서 The C++ Standard Library (Second Edition)Nicolai Josuttis는 람다는 컴파일러가 일반 함수보다 더 잘 최적화 할 수 있다고 말합니다.

또한 C ++ 컴파일러는 일반적인 기능보다 람다를 더 잘 최적화합니다. (213 페이지)

왜 그런 겁니까?

인라인에 관해서는 더 이상 차이가 없어야한다고 생각했습니다. 내가 생각할 수있는 유일한 이유는 컴파일러가 람다와 더 나은 로컬 컨텍스트를 가질 수 있고 더 많은 가정을하고 더 많은 최적화를 수행 할 수 있기 때문입니다.



기본적으로이 명령문 은 람다 만이 아니라 모든 함수 객체에 적용됩니다 .
newacct December

4
함수 포인터도 함수 객체이기 때문에 잘못되었습니다.
Johannes Schaub-litb

2
@litb : 나는 그것에 동의하지 않는다고 생각합니다. wikipedia), 사람들은 함수 객체라고 할 때 호출 가능 클래스를 의미합니다.
Sebastian Mach

1
일부 컴파일러는 일반 함수보다 람다를 더 잘 최적화 할 수 있지만 모두는 아닙니다 :-(
Cody Grey

답변:


175

그 이유는 람다는 함수 객체 이므로 함수 템플릿으로 전달하면 해당 객체에 대한 새로운 함수가 인스턴스화되기 때문입니다. 따라서 컴파일러는 람다 호출을 간단하게 인라인 할 수 있습니다.

반면 함수의 경우 오래된주의 사항이 적용됩니다. 함수 포인터 가 함수 템플릿으로 전달되고 컴파일러는 전통적으로 함수 포인터를 통해 호출을 인라인하는 데 많은 문제가 있습니다. 그들은 이론적으로 인라인 될 수 있지만, 주변 기능뿐만 아니라 인라인 될 경우에만 가능합니다.

예를 들어 다음 함수 템플릿을 고려하십시오.

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특정 함수로 해석 할 수 있습니다 .


4
다른 람다 식으로 동일한 함수 템플릿을 인스턴스화하면 고유 한 유형의 완전히 새로운 함수가 생성되며 이는 단점이 될 수 있습니다.
진정

2
@greggo 절대적으로. 문제는 인라인 될 수없는 기능을 처리 할 때입니다 (너무 커서 너무 커서). 여기서 람다의 경우 콜백에 대한 호출은 여전히 인라인 될 수 있지만 함수 포인터의 경우에는 그렇지 않습니다. std::sort함수 포인터 대신 람다를 사용하는 전형적인 예는 최대 7 배 (아마도 더 많지만 그에 대한 데이터는 없습니다!) 성능이 향상됩니다.
Konrad Rudolph

1
하나는 우리가 람다를 전달하는 : 당신은 여기에 두 가지 기능을 혼동하고 @greggo (예를 들어 std::sort, 또는 map내 예제) 및 람다 자체. 람다는 일반적으로 작습니다. 다른 기능 – 반드시 그런 것은 아닙니다. 우리는 다른 함수 내에서 람다에 대한 호출 인라인하는 것에 관심이 있습니다.
Konrad Rudolph

2
@greggo 알고 있습니다. 이것은 말 그대로 내 대답의 마지막 문장이 말한 것입니다.
Konrad Rudolph

1
그 간단한 부울 함수를 주어진 내가 (그냥 우연히 발견 한) 호기심 발견하는 것은 pred누구의 정의를 볼 수 있습니다, 그리고 GCC의 V5.3을 사용하여, std::find_if(b, e, pred)인라인하지 않습니다 pred,하지만 std::find_if(b, e, [](int x){return pred(x);})않습니다. Clang은 둘 다 인라인을 관리하지만 람다를 사용하여 g ++만큼 빠른 코드를 생성하지 않습니다.
rici

26

"함수"를 알고리즘에 전달할 때 실제로 함수에 대한 포인터를 전달하므로 함수에 대한 포인터를 통해 간접 호출을 수행해야하기 때문입니다. 람다를 사용할 때 객체에서 해당 유형에 대해 특별히 인스턴스화 된 템플릿 인스턴스로 전달되고 람다 함수에 대한 호출은 함수 포인터를 통한 호출이 아니므로 직접 인라인 될 수 있습니다.


5
"람다 함수에 대한 호출은 직접 호출"입니다. 그리고 람다뿐만 아니라 모든 함수 객체에 대해서도 마찬가지입니다 . 그것은 단순히 쉽게 인라인 할 수없는 함수 포인터 일뿐입니다.
Pete Becker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.