표준 :: 기능 대 템플릿 666.0f; } float calc2(std::function<float(float)>

C ++ 11 덕분 std::function에 functor 래퍼 제품군을 받았습니다 . 불행히도, 나는 이러한 새로운 추가 사항에 대한 나쁜 것들만을 계속 듣고 있습니다. 가장 인기있는 것은 그들이 엄청 느리다는 것입니다. 나는 그것을 테스트했으며 템플릿과 비교할 때 정말 빨랐습니다.

#include <iostream>
#include <functional>
#include <string>
#include <chrono>

template <typename F>
float calc1(F f) { return -1.0f * f(3.3f) + 666.0f; }

float calc2(std::function<float(float)> f) { return -1.0f * f(3.3f) + 666.0f; }

int main() {
    using namespace std::chrono;

    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        calc1([](float arg){ return arg * 0.5f; });
    }
    const auto tp2 = high_resolution_clock::now();

    const auto d = duration_cast<milliseconds>(tp2 - tp1);
    std::cout << d.count() << std::endl;
    return 0;
}

111ms 대 1241ms 나는 템플릿이 멋지게 인라인 될 수 있고 function가상 호출을 통해 내부를 덮을 수 있기 때문이라고 생각 합니다.

분명히 템플릿에는 문제가 있습니다.

  • 라이브러리를 닫힌 코드로 해제 할 때 원하지 않는 헤더가 아닌 헤더로 제공되어야합니다.
  • extern template유사한 정책이 도입 되지 않으면 컴파일 시간이 훨씬 길어질 수 있습니다 .
  • 템플릿의 요구 사항 (개념, 누구?)을 나타내는 깨끗한 방법은 없습니다 (적어도 나에게 알려져 있음). 어떤 종류의 functor가 필요한지 설명하는 주석이 있습니다.

따라서 functions 를 통과 함수의 사실상 표준 으로 사용할 수 있고 고성능이 필요한 템플릿을 사용해야한다고 가정 할 수 있습니까?


편집하다:

내 컴파일러는 CTP가 없는 Visual Studio 2012 입니다.



답변

일반적으로 디자인 상황에 직면 한 경우 템플릿을 사용하십시오 . 집중해야 할 것은 사용 사례를 구분하는 것이라고 생각하기 때문에 디자인 이라는 단어를 강조했습니다.std::function 와 템플릿 .

일반적으로 템플릿 선택은 더 넓은 원칙의 예일뿐입니다 . 컴파일 타임에 가능한 한 많은 제약 조건을 지정하십시오 . 이론적 근거는 간단합니다. 오류가 발생하거나 유형이 일치하지 않으면 프로그램이 생성되기 전에 고객에게 버그가있는 프로그램을 제공하지 않습니다.

또한 올바르게 지적했듯이 템플릿 함수에 대한 호출은 정적으로 (즉, 컴파일 타임에) 해결되므로 컴파일러는 코드를 최적화하고 인라인하는 데 필요한 모든 정보를 가지고 있습니다. vtable).

그렇습니다. 템플릿 지원이 완벽하지는 않으며 C ++ 11에는 여전히 개념에 대한 지원이 부족합니다. 그러나 나는 std::function그런 점에서 당신을 어떻게 구할 수 있을지 모르겠습니다 . std::function템플릿의 대안이 아니라 템플릿을 사용할 수없는 디자인 상황을위한 도구입니다.

이러한 사용 사례 중 하나 는 특정 서명을 준수하지만 컴파일 타임에 구체적인 유형을 알 수없는 호출 가능 객체를 호출하여 런타임시 호출을 해결해야 할 때 발생합니다 . 이것은 일반적으로 잠재적으로 다른 유형 의 콜백 모음이 있지만 균일하게 호출 해야하는 경우입니다. . 등록 된 콜백의 유형과 수는 프로그램 상태와 응용 프로그램 논리에 따라 런타임에 결정됩니다. 이러한 콜백 중 일부는 펑 터일 수 있고 일부는 일반 함수일 수 있으며 일부는 다른 함수를 특정 인수에 바인딩 한 결과 일 수 있습니다.

std::function그리고 std::bind또한 가능하게하는 자연 관용구 제공 기능 프로그래밍 기능은 객체로 취급하고 자연스럽게 카레 및 기타 기능을 생성하기 위해 결합되는 C ++에 있습니다. 이러한 종류의 조합은 템플릿으로도 달성 할 수 있지만, 비슷한 디자인 상황은 일반적으로 런타임에 결합 된 호출 가능한 객체의 유형을 결정해야하는 유스 케이스와 함께 제공됩니다.

마지막으로, std::function피할 수없는 다른 상황 이 있습니다. 예를 들어, 반복적 인 람다 를 작성하려는 경우 ; 그러나 이러한 제한은 제가 생각하는 개념적 차이보다는 기술적 한계에 의해 결정됩니다.

요약하면, 디자인에 집중 하고이 두 구성의 개념적 사용 사례가 무엇인지 이해하려고 노력하십시오. 당신이 그랬던 것처럼 그들을 비교해 보면, 그들이 속해 있지 않은 경기장으로 강요하고있는 것입니다.


답변

Andy Prowl은 디자인 문제를 훌륭하게 다루었습니다. 물론 이것은 매우 중요하지만 원래 질문은에 관련된 더 많은 성능 문제와 관련이 있다고 생각합니다 std::function.

우선, 측정 기술에 대한 빠른 언급 : 11ms에 대해 얻은 calc1것은 전혀 의미가 없습니다. 실제로 생성 된 어셈블리 (또는 어셈블리 코드 디버깅)를 살펴보면 VS2012의 옵티마이 저가 호출 결과가 calc1반복과 독립적이며 호출을 루프 밖으로 이동 시킨다는 것을 알기에 충분히 영리하다는 것을 알 수 있습니다 .

for (int i = 0; i < 1e8; ++i) {
}
calc1([](float arg){ return arg * 0.5f; });

또한 통화 calc1는 눈에 띄는 효과가 없으며 전화를 완전히 끊습니다. 따라서 111ms는 빈 루프가 실행되는 시간입니다. (최적화 프로그램이 루프를 유지 한 것에 놀랐습니다.) 따라서 루프의 시간 측정에주의하십시오. 이것은 보이는 것처럼 간단하지 않습니다.

지적했듯이 옵티마이 저는 이해하기가 더 어려우며 std::function호출을 루프 밖으로 이동시키지 않습니다. 따라서 1241ms는 공정한 측정입니다 calc2.

이 공지 사항, std::function호출 다른 유형의 객체를 저장할 수있다. 따라서 스토리지에 대해 유형 삭제 마법을 수행해야합니다. 일반적으로 이는 동적 메모리 할당을 의미합니다 (기본적으로에 대한 호출을 통해 new). 이것은 상당히 비용이 많이 드는 작업으로 잘 알려져 있습니다.

표준 (20.8.11.2.1 / 5)은 고맙게도 VS2012가 수행하는 작은 객체 (특히 원본 코드)에 대한 동적 메모리 할당을 피하기 위해 구현을 권장합니다.

메모리 할당과 관련하여 얼마나 느려질 수 있는지에 대한 아이디어를 얻으려면 람다 식을 3으로 캡처하도록 변경했습니다 float. 이것은 작은 객체 최적화를 적용하기에 호출 가능한 객체를 너무 크게 만듭니다.

float a, b, c; // never mind the values
// ...
calc2([a,b,c](float arg){ return arg * 0.5f; });

이 버전의 경우 시간은 약 16000ms (원래 코드의 경우 1241ms와 비교)입니다.

마지막으로 람다의 수명은 std::function. 이 경우 람다 사본을 저장하는 대신 std::function“참조”를 저장할 수 있습니다. “참조”에 의해 나는 말은 std::reference_wrapper쉽게 기능에 의해 구축되는 std::refstd::cref. 보다 정확하게는 다음을 사용합니다.

auto func = [a,b,c](float arg){ return arg * 0.5f; };
calc2(std::cref(func));

시간은 약 1860ms로 줄어 듭니다.

나는 그것에 대해 얼마 전에 썼다.

http://www.drdobbs.com/cpp/efficient-use-of-lambda-expressions-and/232500059

이 기사에서 언급했듯이 C ++ 11에 대한 지원이 부족하기 때문에 VS2010에는 인수가 적용되지 않습니다. 글을 쓰는 시점에는 베타 버전의 VS2012 만 사용할 수 있었지만 C ++ 11에 대한 지원은 이미이 문제에 충분했습니다.


답변

Clang을 사용하면 둘 사이에 성능 차이가 없습니다.

clang (3.2, 트렁크 166872) (Linux의 경우 -O2) 을 사용하면 두 경우의 이진이 실제로 동일합니다 .

포스트 끝에 클랜으로 돌아 올게요 그러나 먼저 gcc 4.7.2 :

이미 많은 통찰력이 있지만 calc1과 calc2의 계산 결과가 인라인 등으로 인해 동일하지 않다는 것을 지적하고 싶습니다. 예를 들어 모든 결과의 합계를 비교하십시오.

float result=0;
for (int i = 0; i < 1e8; ++i) {
  result+=calc2([](float arg){ return arg * 0.5f; });
}

되는 calc2로

1.71799e+10, time spent 0.14 sec

calc1을 사용하면

6.6435e+10, time spent 5.772 sec

이는 속도 차이가 ~ 40이고 값이 ~ 4입니다. 첫 번째는 OP가 게시 한 것 (Visual Studio 사용)보다 훨씬 큰 차이입니다. 실제로 최종 값을 인쇄하는 것은 컴파일러가 눈에 띄는 결과가없는 코드를 제거하지 못하게하는 것이 좋습니다 (있는 그대로). Cassio Neri는 이미 그의 대답에서 이것을 말했습니다. 결과의 차이에 유의하십시오-다른 계산을 수행하는 코드의 속도 계수를 비교할 때주의해야합니다.

또한 공정하게, f (3.3)를 반복적으로 계산하는 다양한 방법을 비교하는 것은 그리 흥미롭지 않을 것입니다. 입력이 일정하면 루프에 있지 않아야합니다. (옵티마이 저가 쉽게 알 수 있습니다)

사용자 제공 값 인수를 calc1과 2에 추가하면 calc1과 calc2 사이의 속도 계수가 40에서 5의 계수로 내려갑니다! Visual Studio를 사용하면 차이가 2 배에 가까우며, clang을 사용하면 차이가 없습니다 (아래 참조).

또한 곱셈이 빠르기 때문에 감속 요인에 대해 이야기하는 것은 종종 흥미롭지 않습니다. 더 흥미로운 질문은 함수가 얼마나 작고 실제 프로그램에서 병목 현상을 일으키는 것입니까?

그 소리:

Clang (3.2 사용) 은 예제 코드 (아래 게시)에 대해 calc1과 calc2 사이를 뒤집을 때 실제로 동일한 바이너리를 생성했습니다 . 질문에 게시 된 원래 예제를 사용하면 둘 다 동일하지만 시간이 전혀 걸리지 않습니다 (위에서 설명한 것처럼 루프는 완전히 제거됩니다). 내 수정 된 예에서 -O2로 :

실행할 시간 (초) :

clang:        calc1:           1.4 seconds
clang:        calc2:           1.4 seconds (identical binary)

gcc 4.7.2:    calc1:           1.1 seconds
gcc 4.7.2:    calc2:           6.0 seconds

VS2012 CTPNov calc1:           0.8 seconds
VS2012 CTPNov calc2:           2.0 seconds

VS2015 (14.0.23.107) calc1:    1.1 seconds
VS2015 (14.0.23.107) calc2:    1.5 seconds

MinGW (4.7.2) calc1:           0.9 seconds
MinGW (4.7.2) calc2:          20.5 seconds 

모든 바이너리의 계산 결과는 동일하며 모든 테스트는 동일한 머신에서 실행되었습니다. 클랜이나 VS 지식이 깊은 사람이 어떤 최적화가 수행되었는지에 대해 언급 할 수 있다면 흥미로울 것입니다.

수정 된 테스트 코드 :

#include <functional>
#include <chrono>
#include <iostream>

template <typename F>
float calc1(F f, float x) {
  return 1.0f + 0.002*x+f(x*1.223) ;
}

float calc2(std::function<float(float)> f,float x) {
  return 1.0f + 0.002*x+f(x*1.223) ;
}

int main() {
    using namespace std::chrono;

    const auto tp1 = high_resolution_clock::now();

    float result=0;
    for (int i = 0; i < 1e8; ++i) {
      result=calc1([](float arg){
          return arg * 0.5f;
        },result);
    }
    const auto tp2 = high_resolution_clock::now();

    const auto d = duration_cast<milliseconds>(tp2 - tp1);
    std::cout << d.count() << std::endl;
    std::cout << result<< std::endl;
    return 0;
}

최신 정보:

vs2015를 추가했습니다. 또한 calc1, calc2에 이중-> 부동 변환이 있음을 알았습니다. 그것들을 제거해도 Visual Studio의 결론은 바뀌지 않습니다 (둘 다 훨씬 빠르지 만 비율은 거의 같습니다).


답변

다른 것은 다릅니다.

템플릿이 할 수없는 일을하기 때문에 속도가 느립니다. 특히, 주어진 인수 유형으로 호출 할 수 있고 리턴 유형이 동일한 코드에서 주어진 리턴 유형으로 변환 가능한 함수 를 호출 수 있습니다 .

void eval(const std::function<int(int)>& f) {
    std::cout << f(3);
}

int f1(int i) {
    return i;
}

float f2(double d) {
    return d;
}

int main() {
    std::function<int(int)> fun(f1);
    eval(fun);
    fun = f2;
    eval(fun);
    return 0;
}

합니다 같은 기능 객체가 fun에 모두 호출에 전달되고있다 eval. 두 가지 기능이 있습니다.

그렇게 할 필요가 없으면를 사용 하지 않아야 합니다 std::function.


답변

여기에 이미 좋은 대답이 있으므로, std :: function과 템플릿을 비교하는 것은 가상 함수를 함수와 비교하는 것과 같습니다. 가상 함수를 함수에 “선호”하지 말고 문제에 맞을 때 가상 함수를 사용하여 의사 결정을 컴파일 시간에서 런타임으로 이동하십시오. 아이디어는 (점프 테이블과 같은) 맞춤형 솔루션을 사용하여 문제를 해결하는 대신 컴파일러에게 더 나은 최적화 기회를 제공하는 것을 사용한다는 것입니다. 표준 솔루션을 사용하는 경우 다른 프로그래머에게도 도움이됩니다.


답변

이 답변은 기존 답변 세트에 기여하여 std :: function 호출의 런타임 비용에 대한보다 의미있는 벤치 마크라고 생각합니다.

std :: function 메커니즘은 제공하는 항목에 대해 인식되어야합니다. 호출 가능한 엔티티는 적절한 서명의 std :: function으로 변환 될 수 있습니다. z = f (x, y)로 정의 된 함수에 표면을 맞추는 라이브러리가 있다고 가정하고 a를 승인 std::function<double(double,double)>하도록 라이브러리를 작성할 수 있으며 라이브러리 사용자는 호출 가능한 엔티티를 쉽게 변환 할 수 있습니다. 일반적인 함수, 클래스 인스턴스의 메소드 또는 람다 또는 std :: bind에서 지원하는 것입니다.

템플릿 접근 방식과 달리 이것은 다른 경우에 라이브러리 함수를 다시 컴파일하지 않고도 작동합니다. 따라서 추가 사례마다 컴파일 된 코드가 거의 필요하지 않습니다. 항상 이런 일이 가능해졌지만, 어색한 메커니즘이 필요했고, 라이브러리 사용자는 기능을 중심으로 어댑터를 구성해야 작동 할 수 있습니다. std :: function 은 모든 경우에 대해 공통 런타임 호출 인터페이스 를 얻는 데 필요한 모든 어댑터를 자동으로 구성 합니다. 이는 새롭고 매우 강력한 기능입니다.

내 생각에 이것은 성능과 관련하여 std :: function의 가장 중요한 사용 사례입니다. 한 번 구성된 후 std :: function을 여러 번 호출하는 비용에 관심이 있으며 컴파일러가 실제로 호출되는 함수를 알고 호출을 최적화 할 수없는 상황이됩니다 (즉, 적절한 벤치 마크를 얻으려면 다른 소스 파일에서 구현을 숨겨야합니다).

OP와 비슷한 아래 테스트를 수행했습니다. 그러나 주요 변경 사항은 다음과 같습니다.

  1. 각 사례는 10 억 회 반복되지만 std :: function 객체는 한 번만 생성됩니다. 실제 std :: function 호출을 구성 할 때 ‘operator new’가 호출되는 출력 코드를 살펴 보았습니다 (최적화되지 않을 수도 있음).
  2. 테스트는 원하지 않는 최적화를 방지하기 위해 두 개의 파일로 분할됩니다
  3. 내 경우는 다음과 같습니다. (a) 함수가 인라인 됨 (b) 함수가 일반 함수 포인터에 의해 전달됨 (c) 함수는 std :: function (d) 함수가 std :: std :: function으로 감싸 인 바인드

내가 얻는 결과는 다음과 같습니다

  • 사례 (a) (인라인) 1.3 nsec

  • 다른 모든 경우 : 3.3 nsec.

경우 (d)는 약간 느리지 만, 차이 (약 0.05nsec)는 잡음에 흡수됩니다.

결론은 std :: function이 실제 함수에 간단한 ‘바인드’적응이있는 경우에도 함수 포인터를 사용하는 것과 비슷한 호출 오버 헤드입니다. 인라인은 다른 것보다 2ns 빠르지 만 인라인이 런타임에 ‘하드 와이어’된 유일한 경우이기 때문에 예상되는 트레이드 오프입니다.

johan-lundberg의 코드를 동일한 컴퓨터에서 실행하면 루프 당 약 39 nsec가 표시되지만 std :: function의 실제 생성자 및 소멸자를 포함하여 루프에 훨씬 더 많이 있습니다. 새로운 것과 삭제되기 때문에.

-O2 gcc 4.8.1, x86_64 대상 (core i5).

코드는 두 개의 파일로 나누어 져 컴파일러가 호출되는 기능을 확장하지 못하도록합니다 (한 경우를 제외하고).

—– 첫 번째 소스 파일 ————–

#include <functional>


// simple funct
float func_half( float x ) { return x * 0.5; }

// func we can bind
float mul_by( float x, float scale ) { return x * scale; }

//
// func to call another func a zillion times.
//
float test_stdfunc( std::function<float(float)> const & func, int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func(x);
    }
    return y;
}

// same thing with a function pointer
float test_funcptr( float (*func)(float), int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func(x);
    }
    return y;
}

// same thing with inline function
float test_inline(  int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func_half(x);
    }
    return y;
}

—– 두 번째 소스 파일 ————-

#include <iostream>
#include <functional>
#include <chrono>

extern float func_half( float x );
extern float mul_by( float x, float scale );
extern float test_inline(  int nloops );
extern float test_stdfunc( std::function<float(float)> const & func, int nloops );
extern float test_funcptr( float (*func)(float), int nloops );

int main() {
    using namespace std::chrono;


    for(int icase = 0; icase < 4; icase ++ ){
        const auto tp1 = system_clock::now();

        float result;
        switch( icase ){
         case 0:
            result = test_inline( 1e9);
            break;
         case 1:
            result = test_funcptr( func_half, 1e9);
            break;
         case 2:
            result = test_stdfunc( func_half, 1e9);
            break;
         case 3:
            result = test_stdfunc( std::bind( mul_by, std::placeholders::_1, 0.5), 1e9);
            break;
        }
        const auto tp2 = high_resolution_clock::now();

        const auto d = duration_cast<milliseconds>(tp2 - tp1);
        std::cout << d.count() << std::endl;
        std::cout << result<< std::endl;
    }
    return 0;
}

관심있는 사람들을 위해 다음은 ‘mul_by’를 float (float)처럼 보이도록 컴파일러가 빌드 한 어댑터입니다. bind (mul_by, _1,0.5)로 작성된 함수가 호출 될 때 ‘호출’됩니다.

movq    (%rdi), %rax                ; get the std::func data
movsd   8(%rax), %xmm1              ; get the bound value (0.5)
movq    (%rax), %rdx                ; get the function to call (mul_by)
cvtpd2ps    %xmm1, %xmm1        ; convert 0.5 to 0.5f
jmp *%rdx                       ; jump to the func

(바인드에 0.5f를 쓰면 조금 더 빠를 수도 있습니다 …) ‘x’매개 변수는 % xmm0에 도착하고 그대로 유지됩니다.

다음은 test_stdfunc를 호출하기 전에 함수가 구성된 영역의 코드입니다. c ++ filt를 통해 실행하십시오.

movl    $16, %edi
movq    $0, 32(%rsp)
call    operator new(unsigned long)      ; get 16 bytes for std::function
movsd   .LC0(%rip), %xmm1                ; get 0.5
leaq    16(%rsp), %rdi                   ; (1st parm to test_stdfunc)
movq    mul_by(float, float), (%rax)     ; store &mul_by  in std::function
movl    $1000000000, %esi                ; (2nd parm to test_stdfunc)
movsd   %xmm1, 8(%rax)                   ; store 0.5 in std::function
movq    %rax, 16(%rsp)                   ; save ptr to allocated mem

   ;; the next two ops store pointers to generated code related to the std::function.
   ;; the first one points to the adaptor I showed above.

movq    std::_Function_handler<float (float), std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_invoke(std::_Any_data const&, float), 40(%rsp)
movq    std::_Function_base::_Base_manager<std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation), 32(%rsp)


call    test_stdfunc(std::function<float (float)> const&, int)

답변

나는 당신의 결과가 매우 흥미로워 서 무슨 일이 일어나고 있는지 이해하기 위해 조금 파고 들었습니다. 첫째로 많은 다른 사람들이 계산 결과를 얻지 못하면서 컴파일러가 프로그램 상태를 최적화 할 것이라고 말했습니다. 두 번째로 콜백에 대한 무장으로 상수 3.3을 부여하면 다른 최적화가 진행될 것으로 생각됩니다. 이를 염두에두고 벤치 마크 코드를 약간 변경했습니다.

template <typename F>
float calc1(F f, float i) { return -1.0f * f(i) + 666.0f; }
float calc2(std::function<float(float)> f, float i) { return -1.0f * f(i) + 666.0f; }
int main() {
    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        t += calc2([&](float arg){ return arg * 0.5f + t; }, i);
    }
    const auto tp2 = high_resolution_clock::now();
}

이 변경 사항을 gcc 4.8 -O3으로 컴파일하고 calc1의 경우 330ms, calc2의 경우 2702를 얻었습니다. 따라서 템플릿을 사용하는 것이 8 배 빨랐습니다.이 숫자는 나에게 의심 스러웠습니다 .8의 거듭 제곱 속도는 종종 컴파일러가 무언가를 벡터화했음을 나타냅니다. 템플릿 버전에 대해 생성 된 코드를 보면 명확하게 벡터화되었습니다.

.L34:
cvtsi2ss        %edx, %xmm0
addl    $1, %edx
movaps  %xmm3, %xmm5
mulss   %xmm4, %xmm0
addss   %xmm1, %xmm0
subss   %xmm0, %xmm5
movaps  %xmm5, %xmm0
addss   %xmm1, %xmm0
cvtsi2sd        %edx, %xmm1
ucomisd %xmm1, %xmm2
ja      .L37
movss   %xmm0, 16(%rsp)

std :: function 버전이 아닌 곳. 템플릿을 사용하면 컴파일러가 루프 전체에서 함수가 절대 변경되지 않지만 std :: function이 전달되면 변경 될 수 있으므로 벡터화 할 수 없다는 것을 알고 있기 때문에 이것은 나에게 의미가 있습니다.

이것은 컴파일러가 std :: function 버전에서 동일한 최적화를 수행 할 수 있는지 확인하기 위해 다른 것을 시도하게했습니다. 함수를 전달하는 대신 std :: function을 전역 var로 만들고 이것을 호출했습니다.

float calc3(float i) {  return -1.0f * f2(i) + 666.0f; }
std::function<float(float)> f2 = [](float arg){ return arg * 0.5f; };

int main() {
    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        t += calc3([&](float arg){ return arg * 0.5f + t; }, i);
    }
    const auto tp2 = high_resolution_clock::now();
}

이 버전에서는 컴파일러가 이제 코드를 같은 방식으로 벡터화했으며 동일한 벤치 마크 결과를 얻었습니다.

  • 템플릿 : 330ms
  • std :: function : 2702ms
  • 글로벌 표준 :: 기능 : 330ms

내 결론은 std :: function의 원시 속도와 템플릿 functor의 속도가 거의 동일하다는 것입니다. 그러나 최적화 프로그램의 작업을 훨씬 더 어렵게 만듭니다.