2010-11-06 14 views
8

Wiem, że polimorfizm może dodać zauważalny narzut. Wywołanie funkcji wirtualnej jest wolniejsze niż wywołanie funkcji innej niż wirtualna. (Całe moje doświadczenie dotyczy GCC, ale myślę/słyszałem, że tak jest w przypadku każdego prawdziwego kompilatora.)C++: obciążenie polimorfizmem narzutowym

Wiele razy dana funkcja wirtualna jest wielokrotnie wywoływana na tym samym obiekcie; Wiem, że typ obiektu nie ulegnie zmianie, a większość czasu kompilator może łatwo odliczenia, która ma dobrze:

BaseType &obj = ...; 
while(looping) 
    obj.f(); // BaseType::f is virtual 

Aby przyspieszyć kod mógłbym przepisać powyższy kod tak:

BaseType &obj = ...; 
FinalType &fo = dynamic_cast< FinalType& >(obj); 
while(looping) 
    fo.f(); // FinalType::f is not virtual 

Zastanawiam się, jaki jest najlepszy sposób, aby uniknąć tego obciążenia z powodu polimorfizmu w tych przypadkach.

Idea górnego rzucania (jak pokazano w drugim fragmencie) nie wygląda mi dobrze: BaseType może zostać odziedziczony przez wiele klas, a próba wyrzucenia wszystkich do nich byłaby całkiem nie do opisania.

Innym pomysłem może być przechowywanie obj.f w wskaźniku funkcji (nie przetestowałem tego, nie jestem pewien, czy zabiłoby to czas działania programu), ale znowu ta metoda nie wygląda idealnie: jak powyższa metoda, wymagałoby napisania większej ilości kodu i nie byłaby w stanie wykorzystać niektórych optymalizacji (np. jeśli FinalType::f byłaby funkcją inline, nie zostałoby to zainicjowane - ale myślę, że jedynym sposobem na uniknięcie tego byłaby górna obsada obj do ostatecznego typu ...)

Czy istnieje lepsza metoda?

Edytuj: Oczywiście, nie będzie to miało tak wielkiego wpływu. Pytanie to dotyczyło głównie tego, czy było coś do zrobienia, ponieważ wygląda na to, że ten narzut jest podany za darmo (ten koszt wydaje się bardzo łatwy do zabicia). Nie rozumiem, dlaczego nie.

Łatwe słowo kluczowe dla małych optymalizacji, takie jak C99 restrict, aby powiedzieć kompilatorowi, że polimorficzny obiekt ma stały typ, to było to, na co liczyłem.

W każdym razie, tylko aby odpowiedzieć na komentarze, obecny jest niewielki narzut. Spójrz na to skrajnego kodu ad-hoc:

struct Base { virtual void f(){} }; 
struct Final : public Base { void f(){} }; 

int main() { 
    Final final; 
    Final &f = final; 
    Base &b = f; 

    for(int i = 0; i < 1024*1024*1024; ++ i) 
#ifdef BASE 
     b.f(); 
#else 
     f.f(); 
#endif 

    return 0; 
} 

Kompilacja i uruchomienie go, biorąc czasy:

$ for OPT in {"",-O0,-O1,-O2,-O3,-Os}; do 
    for DEF in {BASE,FINAL}; do 
     g++ $OPT -D$DEF -o virt virt.cpp && 
     TIME="$DEF $OPT: %U" time ./virt; 
    done; 
    done   
BASE : 5.19                                           
FINAL : 4.21                                           
BASE -O0: 5.22                                          
FINAL -O0: 4.19                                          
BASE -O1: 3.55                                          
FINAL -O1: 1.53                                          
BASE -O2: 3.61                                          
FINAL -O2: 0.00                                          
BASE -O3: 3.58                                          
FINAL -O3: 0.00                                          
BASE -Os: 6.14                                          
FINAL -Os: 0.00 

Chyba tylko -O2, -O3 i -Os są inline Final::f.

Te testy zostały przeprowadzone na moim komputerze, z najnowszym procesorem GCC i procesorem AMD Athlon (tm) 64 X2 Dual Core 4000+. Wydaje mi się, że na tańszej platformie może być dużo wolniej.

+9

Podejrzewam, że mówisz, że twój kod się wczytuje - powolutku, a profilowałeś go i stwierdziłeś, że problem tkwi w polimorfizmie? – wilhelmtell

+2

Jeśli 'f' jest wirtualny w' BaseType' i 'FinalType' pochodzi od' BaseType', to 'f' jest również wirtualne w' FinalType'. –

+1

Również. 'dynamic_cast <>()' ma koszt kontroli w środowisku wykonawczym, a koszt polimorfizmu jest pojedynczym dereferencją wskaźnika. Sugeruję za każdym razem, gdy mówisz słowo "napowietrzne", upewnij się, że ** dokładnie ** jakie to obciążenie, przynajmniej za pierwszym razem, gdy mówisz o tym narzut. Po prostu wiemy, co próbujemy tutaj wyeliminować. A teraz, rozumiem, że wyprofilowałeś oba podejścia i okazało się, że polimorfizm jest wolniejszy niż twój hack? – wilhelmtell

Odpowiedz

8

Jeśli wysyłka dynamiczna jest wąskim gardłem wydajności w twoim programie, to sposobem rozwiązania problemu nie jest użycie dynamicznej wysyłki (nie korzystaj z funkcji wirtualnych).

Można zastąpić niektóre polimorfizmy czasu wykonywania polimorfizmem czasu kompilacji za pomocą szablonów i ogólnego programowania zamiast funkcji wirtualnych. Może to, ale nie musi, skutkować lepszą wydajnością; tylko profiler może ci powiedzieć na pewno.

Jasne jest, jak to już zauważył Wilhelmtell w komentarzach do tego pytania, rzadko zdarza się, aby narzut związany z dynamiczną wysyłką był na tyle znaczący, aby się martwić. Bądź absolutnie pewny, że to Twoja najlepsza wydajność, zanim zastąpisz wbudowaną wygodę nieporęczną niestandardową implementacją.

+1

Czasami jesteś "zmuszony" do użycia polimorfizmu (a tym samym dynamicznej wysyłki, do tej pory brakowało mi tego terminu). Oczywiście możesz górny rzutować swój polimorficzny wskaźnik/referencję, a następnie użyć go (przekazać do funkcji szablonu lub cokolwiek innego); to właśnie miałem na myśli przy pomocy rozwiązania do górnego rzucania. Jak powiedzieliśmy po edycji oryginalnego posta, jest to głównie wątpliwość co do tego, jak działa. Dzięki za odpowiedź, przy okazji. – peoro

2

Jeśli potrzebujesz polimorfizmu, użyj go. Naprawdę nie ma na to szybszego sposobu.

Odpowiedziałbym jednak na inne pytanie: czy to jest twój największy problem? Jeśli tak, twój kod jest już optymalny lub prawie taki. Jeśli nie, dowiedz się, jaki jest największy problem i zamiast tego skoncentruj się na tym.

Powiązane problemy