2012-12-06 6 views
4

몇 가지 타이밍 테스트를 수행하고 있으며 테스트 중 하나는 다양한 기능 호출 방법을 비교하는 것이 었습니다. 다양한 기능을 사용하여 N 개의 함수를 호출했습니다. 정규 함수 호출, 가상 함수 호출, 함수 포인터 및 boost :: function을 시도했습니다.부스트 :: 기능이 느린 이유는 무엇입니까?

Linux에서 gcc 및 -O3 최적화를 사용했습니다.

가상 호출이 일반 함수 호출보다 느립니다. 그러나 놀랍게도 boost :: function은 가상 호출보다 33 % 느리게 작동합니다.

누구나 눈치 채셨습니까? 왜이게 어떤 단서입니까? 가능하면

+0

가상 함수의 여러 변형이 구현되어 있는지 확인 했습니까? 컴파일러는 일종의 까다 롭고 일종의 클래스가 사실상 가상 함수를 구현한다고 확신 할 경우 유형을 추론 할 수 있습니다. – ltjax

+0

차이점은 정규 함수 호출이고 가상 함수 호출은 크기 때문에 그 중 아무 것도 없었다고 생각하지 않았습니다. 충분히 까다로운 컴파일러 최적화가 일어나지 않는다고 나는 만족했다. 귀하의 질문에 –

+1

하지 응답, 성능을 위해 벽에 있다면, 당신은 덜 느린 대안에 관심이있을 수 : [빠른 위임] [1] [1] : HTTP : // –

답변

6

정기적 인 기능 는 컴파일러에 의해를 인라인 할 수 있지만, boost::function는 인라인되지 않을 수 있습니다. 그것은 하나의 큰 차이입니다.

두 번째 차이점은 boost::functiontype-erasure을 구현합니다. 즉, 간접 참조을 사용하여 실제 기능을 호출합니다. 즉, 먼저 가상 함수를 호출하여 함수를 호출하는 것을 의미합니다. 그래서 일반적으로 최소한 두 개의 함수 호출을 포함합니다 (그 중 하나는 virtual입니다). 그것은 거대한 차이입니다.

는 그래서이 분석을 바탕으로, 하나는 (심지어 테스트 코드를 작성하지 않고)이 추론 수 : 테스트 코드에서, 참의 경우

slowest ------------------------------------------------------> fastest 
     boost::function < virtual function < regular function 
slowest ------------------------------------------------------> fastest 

합니다.

std::function도 마찬가지입니다 (C++ 11부터 사용 가능함).

+0

'boost :: function'을 호출하는 것은 결코 하나의 함수 호출이 아닙니다. –

+0

그것은 C++ 11 람다에도 적용됩니까? – GreyGeek

+0

@GreyGeek 아니, 왜냐하면 람다는'std :: function'이 아니기 때문이다. 사실, 호출 된 함수가 변수의 형태로 노출되기 때문에 C 스타일의 함수 포인터보다 더 효율적일 수 있습니다. – Yakk

2

boost::function은 기능 포인터뿐만 아니라 가상 포인터 operator()을 호출하는 임의의 개체의 전체 복사본을 포함 할 수 있습니다.

어떻게 작동하는지 이해하는 데 도움이 될 수 있습니다 (설명 자료).

다음
struct helper_base { virtual void do_it() = 0; }; 
template<typename Func> 
struct helper:helper_base { 
    Func func; 
    helper(Func f):func(f) {} 
    virtual void do_it() override { func(); } 
}; 
struct do_something_later { 
    boost::unique_ptr<helper_base> pImpl; 
    template<typename Func> 
    do_something_later(Func f):pImpl(make_shared<helper<Func>>(f)) 
    {} 
    void operator()() { (*pImpl).do_it(); } 
private: 
    do_something_later(do_something_later const&); // deleted 
    void operator=(do_something_later const&); // deleted 
}; 

do_something_later는 임의의 객체 (Func을)를 소요하고 수요에에 operator()를 호출 여기

boost::function 형 트릭의 장난감 구현입니다. operator()을 타입 삭제 도우미에 랩 한 다음 가상 함수를 통해 operator()을 호출합니다.

Func 유형은 함수 포인터 일 수도 있고 상태가있는 펑터 일 수도 있습니다. 연산자()로 복사 할 수있는 것은 공정한 게임입니다. do_something_later의 사용자에 관한 한, 이진 인터페이스는 하나뿐입니다.

boost::function (및 std::function)은 가능한 많은 인터페이스를 하나의 인터페이스로 바꾸기 위해 기본적으로이 기술을 사용합니다 (많은 개선 사항이 있음). 비용은 virtual 함수 (또는 동일한 수준의 간접 참조) 호출과 관련됩니다.

+0

좋은 설명. 왜 가상 디스패치가 필요한지 궁금합니다. 왜 도우미를 건너 뛰지 않고 struct do_something_later에 직접 Func을 포함시켜야합니까? –

+1

왜냐하면'do_something_later'는'Func'의 타입에 템플릿을 사용해야 할 것이기 때문입니다. 대신에 생성자 만이합니다. 위의 코드는'Func' 객체가 얼마나 큰지 알고 각각의'Func' 타입에 대한 커스텀'헬퍼 (helper) '를 만들고'operator()'의 서명이 무엇인지 알고 있습니다 (가상입니까? 실제로'int'를 리턴합니까? 스택에 반환 값을위한 공간이 필요합니까? 실제로는 std :: vector 을 반환합니까? 반환 된 객체를'do_it()'에서 정리해야합니까?) – Yakk

0

관찰 된 속도 저하의 진정한 이유는 boost::function이 두 개의 방향을 지정하는 것 외에 포인터를 0과 비교한다는 것입니다.이 테스트가 생략되면 호출은 가상 함수만큼 빠르게 수행됩니다 (이 두 방향은 vtable에 대한 포인터와 실제 함수에 대한 포인터입니다).

관련 문제