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.
Podejrzewam, że mówisz, że twój kod się wczytuje - powolutku, a profilowałeś go i stwierdziłeś, że problem tkwi w polimorfizmie? – wilhelmtell
Jeśli 'f' jest wirtualny w' BaseType' i 'FinalType' pochodzi od' BaseType', to 'f' jest również wirtualne w' FinalType'. –
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