이 답변은 기존 답변 세트에 기여하여 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와 비슷한 아래 테스트를 수행했습니다. 그러나 주요 변경 사항은 다음과 같습니다.
- 각 사례는 10 억 회 반복되지만 std :: function 객체는 한 번만 생성됩니다. 실제 std :: function 호출을 구성 할 때 'operator new'가 호출되는 출력 코드를 살펴 보았습니다 (최적화되지 않을 수도 있음).
- 테스트는 원하지 않는 최적화를 방지하기 위해 두 개의 파일로 분할됩니다
- 내 경우는 다음과 같습니다. (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)
std::function
실제로 이기종 호출 가능 오브젝트 콜렉션이 필요한 경우에만 사용하십시오 (즉, 런타임시 더 이상의 식별 정보를 사용할 수 없음).