2010-06-29 4 views
2

한 수준의 단일 상속에서 가상 함수의 오버 헤드를 결정하기위한 (아마도 매우 비과학적인) 작은 테스트를 설정했고, 얻은 결과는 파생 함수에 액세스 할 때 정확히 동일했습니다 클래스를 다형 적으로 또는 직접 액세스 할 때. 약간의 놀라운 점은 함수가 가상으로 선언 될 때 도입 된 계산 시간의 크기의 순서 (아래 결과 참조)였습니다.가상 함수의 오버 헤드 테스트

멤버 함수를 선언 할 때 오버 헤드가 많이 발생하며 파생 클래스에 직접 액세스 할 때도 여전히 왜 존재합니까? 다음과 같이

코드는 다음과 같습니다

class base 
{ 
public: 
    virtual ~base() {} 
    virtual uint func(uint i) = 0; 
}; 

class derived : public base 
{ 
public: 
    ~derived() {} 
    uint func(uint i) { return i * 2; } 
}; 

uint j = 0; 
ulong k = 0; 
double l = 0; 
ushort numIters = 10; 
base* mybase = new derived; // or derived* myderived = ... 

for(ushort i = 0; i < numIters; i++) 
{ 
    clock_t start2, finish2; 
    start2 = clock(); 

    for (uint j = 0; j < 100000000; ++j) 
     k += mybase->func(j); 

    finish2 = clock(); 
    l += (double) (finish2 - start2); 
    std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl; 

} 

std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl; 
std::cout << "Average duration: " << l/numIters << " ms." << std::endl; 

결과 :

base* mybase = new derived;가 ~ 338 밀리의 평균을 제공합니다.

derived* myderived = new derived;은 평균 ~ 338ms를 제공합니다.

상속을 제거하고 가상 함수를 제거하면 평균 ~ 38 ms가됩니다.

거의 10 배 적습니다. 그래서 기본적으로 어떤 함수가 가상으로 선언된다면 비록 그것이 다형 적으로 사용되지 않더라도 오버 헤드는 항상 동일하게 존재할 것입니다.

감사합니다.

+0

상속 + 가상 함수 비용을 함께 계산하는 것 같습니다. 가상 함수가없는 파생 클래스와 기본 클래스를 인스턴스화해야하는지 테스트해야합니다. –

+0

왜 'new'를 사용합니까? 스택에있는 객체를 인스턴스화하는 것이 더 간단 할 것입니다 ... –

+0

이 경우에는 아무런 차이가 없습니다. 포인터를 통해 액세스하는 경우 동일한 오버 헤드가 발생합니다. –

답변

6

"직접"액세스하는 것은 "간접적으로"액세스하는 것과 동일한 작업을 수행합니다.

myderived에서 함수를 호출하면 거기에 저장된 포인터가 derived에서 파생 된 일부 클래스의 개체를 가리킬 수 있습니다. 컴파일러는 실제적으로 derived 객체라고 추측 할 수 없으며 가상 함수를 재정의하는 추가 파생 클래스의 객체 일 수 있으므로 mybase 경우와 같이 가상 함수 디스패치가 필요합니다. 두 경우 모두 가상 함수 표가 호출되기 전에 함수가 조회됩니다.

포인터를 사용하지 않는 기능 비 다형를 호출하려면 : 당신은 기본적으로 간단한 루프를 끝낼 수 있도록 가상 기능을 제거하면

derived myderived; 
myderived.func(1); 

, 컴파일러는 함수 호출을 인라인 할 수 있습니다 : 당신이 100,000,000 함수 호출의 오버 헤드를 저장하고 컴파일러도 거기에 함수 호출이 있다면 그렇지 않은 것 방법으로 더 루프를 최적화 할 수있을 것 때문에

for (uint j = 0; j < 100000000; ++j) 
    k += i * 2; 

이 훨씬 빠릅니다.

함수가 실제 작업을 수행하면 인라인 버전과 가상 함수 호출 간의 차이가 훨씬 줄어 듭니다. 이 예제에서 함수 본문은 거의 시간이 필요하지 않으므로 함수를 호출하는 데 드는 비용이 본문을 실행하는 데 드는 비용보다 큽니다.

+0

감사합니다. 당신이 말한 것은 완벽한 의미입니다. 기능이 너무 적어서 요점에 동의했습니다. 비율은 여기서 분명히 비뚤어 지지만 함수 호출 오버 헤드를 아는 것이 좋습니다. 따라서 기본적으로 일단 함수 가상을 선언하고 가상 함수를 사용하는 모든 가격이 아니라면 거의 지불 한 포인터를 통해 사용합니다. –

+0

s/컴파일러는 실제로 파생 된 것으로 가정 할 수 없습니다./컴파일러는 실제로 파생 된 /이라고 가정하지 않습니다. 더 많은 클래스가 파생되지 않았으므로 가능합니다 (참고 : 전체 프로그램 최적화가 필요함) – MSalters

+0

포인터를 제거하려고했는데 포인터가 느려졌습니다. –

2

가상 기능 비용은 기본적으로 들지 않습니다. 실제 성능 문제는 불필요한 호출 트리가 문제라고 생각하지 않는 일을 수행하여 발생합니다.

내가 찾는 방법은 디버거에서 앱을 여러 번 일시 중지하고 호출 스택을 포함하여 상태를 검사하는 것입니다. Here's an example은 43x 속도 향상을 위해이 방법을 사용하고 있습니다.

+0

감사합니다. 마이크는 훌륭한 방법입니다. –