Funkcje wirtualne i wydajność - C ++


125

W moich projektach klas intensywnie korzystam z klas abstrakcyjnych i funkcji wirtualnych. Miałem wrażenie, że funkcje wirtualne wpływają na wydajność. Czy to prawda? Ale myślę, że ta różnica w wydajności nie jest zauważalna i wygląda na to, że wykonuję przedwczesną optymalizację. Dobrze?


Zgodnie z moją odpowiedzią sugeruję zamknięcie tego jako duplikatu stackoverflow.com/questions/113830
Suma,


2
Jeśli wykonujesz obliczenia o wysokiej wydajności i przetwarzasz liczby, nie używaj wirtualności w rdzeniu obliczeń: zdecydowanie zabija wszystkie wydajności i zapobiega optymalizacji w czasie kompilacji. Nie ma to znaczenia przy inicjalizacji lub finalizacji programu. Podczas pracy z interfejsami możesz korzystać z wirtualności według własnego uznania.
Vincent,

Odpowiedzi:


90

Praktyczna zasada:

To nie jest problem z wydajnością, dopóki nie możesz tego udowodnić.

Użycie funkcji wirtualnych będzie miało bardzo niewielki wpływ na wydajność, ale jest mało prawdopodobne, aby wpłynęło na ogólną wydajność aplikacji. Lepszymi miejscami do poszukiwania ulepszeń wydajności są algorytmy i operacje we / wy.

Doskonały artykuł, który mówi o funkcjach wirtualnych (i nie tylko), to wskaźniki funkcji składowych i najszybsze możliwe delegaty C ++ .


A co z czystymi funkcjami wirtualnymi? Czy w jakikolwiek sposób wpływają na wydajność? Zastanawiam się tylko, jak się wydaje, że są tam po prostu po to, aby wymusić wdrożenie.
thomthom

2
@thomthom: Dobrze, nie ma różnicy w wydajności między czystymi wirtualnymi a zwykłymi funkcjami wirtualnymi.
Greg Hewgill,

168

Twoje pytanie zaciekawiło mnie, więc poszedłem naprzód i przeprowadziłem taktowanie na procesorze PowerPC 3GHz w kolejności, z którym pracujemy. Test, który przeprowadziłem, polegał na stworzeniu prostej klasy wektorowej 4d z funkcjami get / set

class TestVec 
{
    float x,y,z,w; 
public:
    float GetX() { return x; }
    float SetX(float to) { return x=to; }  // and so on for the other three 
}

Następnie ustawiłem trzy tablice, z których każda zawierała 1024 tych wektorów (wystarczająco małe, aby zmieścić się w L1) i uruchomiłem pętlę, która dodała je do siebie (Ax = Bx + Cx) 1000 razy. Pobiegłem to z funkcjami zdefiniowanymi jako inline, virtualoraz regularnych połączeń funkcyjnych. Oto wyniki:

  • inline: 8 ms (0,65ns na połączenie)
  • bezpośredni: 68 ms (5,53ns na połączenie)
  • wirtualny: 160 ms (13ns na połączenie)

Tak więc w tym przypadku (gdzie wszystko mieści się w pamięci podręcznej) wywołania funkcji wirtualnych były około 20 razy wolniejsze niż wywołania wbudowane. Ale co to naprawdę oznacza? Każde przejście przez pętlę powodowało dokładnie 3 * 4 * 1024 = 12,288wywołania funkcji (1024 wektory razy cztery składniki razy trzy wywołania na dodanie), więc te czasy reprezentują 1000 * 12,288 = 12,288,000wywołania funkcji. Wirtualna pętla trwała 92 ms dłużej niż bezpośrednia pętla, więc dodatkowe obciążenie na wywołanie wyniosło 7 nanosekund na funkcję.

Z tego dochodzę do wniosku: tak , funkcje wirtualne są znacznie wolniejsze niż funkcje bezpośrednie i nie , chyba że planujesz wywoływać je dziesięć milionów razy na sekundę, to nie ma znaczenia.

Zobacz też: porównanie wygenerowanego zespołu.


Ale jeśli dzwoni się do nich wiele razy, często mogą być tańsze niż w przypadku jednego połączenia. Zobacz mój nieistotny blog: phresnel.org/blog , posty zatytułowane „Funkcje wirtualne uważane za nieszkodliwe”, ale oczywiście zależy to od złożoności twoich ścieżek kodowych
Sebastian Mach

22
Mój test mierzy niewielki zestaw funkcji wirtualnych wywoływanych wielokrotnie. W Twoim poście na blogu założono, że czasowy koszt kodu można mierzyć za pomocą operacji liczenia, ale nie zawsze jest to prawdą; Głównym kosztem vfunc na nowoczesnych procesorach jest bańka rurociągu spowodowana błędną prognozą gałęzi.
Crashworks

10
byłby to świetny punkt odniesienia dla LTO gcc (Optymalizacja czasu łącza); spróbuj skompilować to ponownie z włączonym lto : gcc.gnu.org/wiki/LinkTimeOptimization i zobacz, co się stanie z czynnikiem 20x
lurscher

1
Jeśli klasa ma jedną funkcję wirtualną i jedną wbudowaną, czy wpłynie to również na wydajność metody niewirtualnej? Po prostu z natury wirtualnej klasy?
thomthom

4
@thomthom Nie, wirtualny / niewirtualny to atrybut dla funkcji. Funkcję należy zdefiniować za pośrednictwem vtable tylko wtedy, gdy jest oznaczona jako wirtualna lub zastępuje klasę bazową, która ma ją jako wirtualną. Często zobaczysz klasy, które mają grupę funkcji wirtualnych dla interfejsu publicznego, a następnie wiele wbudowanych akcesorów i tak dalej. (Technicznie jest to specyficzne dla implementacji i kompilator mógłby używać wirtualnych ponterów nawet dla funkcji oznaczonych jako „inline”, ale osoba, która napisałaby taki kompilator, byłaby szalona.)
Crashworks

42

Kiedy Objective-C (gdzie wszystkie metody są wirtualne) jest podstawowym językiem dla iPhone'a, a dziwaczna Java jest głównym językiem dla Androida, myślę, że używanie wirtualnych funkcji C ++ na naszych dwurdzeniowych wieżach 3 GHz jest całkiem bezpieczne.


4
Nie jestem pewien, czy iPhone jest dobrym przykładem wydajnego kodu: youtube.com/watch?v=Pdk2cJpSXLg
Crashworks,

13
@ Crashworks: iPhone nie jest w ogóle przykładem kodu. To przykład sprzętu - szczególnie wolnego sprzętu , o czym tutaj mówiłem. Jeśli te rzekomo „wolne” języki są wystarczająco dobre dla słabo zasilanego sprzętu, funkcje wirtualne nie będą stanowić dużego problemu.
Chuck

52
IPhone działa na procesorze ARM. Procesory ARM używane w systemie iOS zostały zaprojektowane z myślą o niskim MHz i niskim zużyciu energii. Nie ma krzemu do przewidywania rozgałęzień na procesorze, a zatem nie ma narzutu wydajności wynikającego z błędów przewidywania rozgałęzień wywołanych wywołaniami funkcji wirtualnych. Również MHz dla sprzętu iOS jest na tyle niskie, że brak pamięci podręcznej nie powoduje zatrzymania procesora na 300 cykli zegara podczas pobierania danych z pamięci RAM. Chybienia pamięci podręcznej są mniej ważne przy niższych MHz. Krótko mówiąc, korzystanie z funkcji wirtualnych na urządzeniach z systemem iOS nie wiąże się z dodatkowymi kosztami, ale jest to problem sprzętowy i nie dotyczy procesorów komputerów stacjonarnych.
HaltingState

4
Jako wieloletni programista Java, który niedawno przeszedł do C ++, chciałbym dodać, że kompilator JIT i optymalizator czasu wykonywania w Javie mają możliwość kompilowania, przewidywania, a nawet wbudowywania niektórych funkcji w czasie wykonywania po określonej liczbie pętli. Jednak nie jestem pewien, czy C ++ ma taką funkcję w czasie kompilacji i łączenia, ponieważ brakuje mu wzorca wywołania w czasie wykonywania. Dlatego w C ++ możemy być bardziej ostrożni.
Alex Suo,

@AlexSuo Nie jestem pewien, o co ci chodzi? Po skompilowaniu C ++ oczywiście nie może optymalizować na podstawie tego, co może się zdarzyć w czasie wykonywania, więc przewidywanie itp. Musiałoby być wykonane przez sam procesor ... ale dobre kompilatory C ++ (jeśli zostały poinstruowane) robią wszystko, aby zoptymalizować funkcje i pętle na długo wcześniej runtime.
underscore_d

34

W aplikacjach o krytycznym znaczeniu dla wydajności (takich jak gry wideo) wywołanie funkcji wirtualnej może być zbyt wolne. W przypadku nowoczesnego sprzętu największym problemem dotyczącym wydajności jest brak pamięci podręcznej. Jeśli danych nie ma w pamięci podręcznej, mogą minąć setki cykli, zanim będą dostępne.

Normalne wywołanie funkcji może wygenerować brak pamięci podręcznej instrukcji, gdy procesor pobiera pierwszą instrukcję nowej funkcji i nie ma jej w pamięci podręcznej.

Wywołanie funkcji wirtualnej musi najpierw załadować wskaźnik vtable z obiektu. Może to spowodować utratę pamięci podręcznej danych. Następnie ładuje wskaźnik funkcji z tabeli vtable, co może skutkować kolejnym brakiem pamięci podręcznej danych. Następnie wywołuje funkcję, która może spowodować pominięcie pamięci podręcznej instrukcji, podobnie jak funkcja niewirtualna.

W wielu przypadkach dwa dodatkowe chybienia w pamięci podręcznej nie stanowią problemu, ale w ścisłej pętli kodu krytycznego dla wydajności może znacznie zmniejszyć wydajność.


6
Zgadza się, ale każdy kod (lub vtable), który jest wielokrotnie wywoływany ze ścisłej pętli, będzie (oczywiście) rzadko cierpiał na błędy pamięci podręcznej. Poza tym wskaźnik vtable zwykle znajduje się w tej samej linii pamięci podręcznej, co inne dane w obiekcie, do których wywoływana metoda będzie miała dostęp, więc często mówimy o tylko jednym dodatkowym braku pamięci podręcznej.
Qwertie

5
@Qwertie Nie sądzę, żeby to było konieczne. Ciało pętli (jeśli jest większe niż pamięć podręczna L1) mogłoby „wycofać” wskaźnik tabeli vtable, wskaźnik funkcji i następna iteracja musiałaby czekać na dostęp do pamięci podręcznej L2 (lub więcej) w każdej iteracji
Ghita

30

Ze strony 44 podręcznika „Optimizing Software in C ++” firmy Agner Fog :

Czas potrzebny na wywołanie wirtualnej funkcji składowej to o kilka cykli zegara więcej niż wywołanie niewirtualnej funkcji składowej, pod warunkiem, że instrukcja wywołania funkcji zawsze wywołuje tę samą wersję funkcji wirtualnej. Jeśli wersja ulegnie zmianie, otrzymasz karę za błędną prognozę w wysokości 10-30 cykli zegara. Zasady przewidywania i błędnego przewidywania wywołań funkcji wirtualnych są takie same, jak w przypadku instrukcji switch ...


Dzięki za tę referencję. Podręczniki optymalizacji Agner Fog to złoty standard optymalnego wykorzystania sprzętu.
Arto Bendiken

Opierając się na moich wspomnieniach i szybkim wyszukiwaniu - stackoverflow.com/questions/17061967/c-switch-and-jump-tables - wątpię, aby to zawsze było prawdą switch. caseOczywiście z całkowicie arbitralnymi wartościami. Ale jeśli wszystkie casesą następujące po sobie, kompilator może być w stanie zoptymalizować to do postaci tabeli skoków (ach, to przypomina mi stare dobre dni Z80), która powinna być (z braku lepszego terminu) stała. Nie żebym polecał próbę zastąpienia vfuncs switch, co jest niedorzeczne. ;)
underscore_d

7

absolutnie. Dawno temu był to problem, gdy komputery pracowały z częstotliwością 100 MHz, ponieważ każde wywołanie metody wymagało przeszukania tabeli vtable przed jej wywołaniem. Ale dzisiaj… na procesorze 3 GHz z pamięcią podręczną pierwszego poziomu z większą ilością pamięci niż mój pierwszy komputer? Ani trochę. Przydzielanie pamięci z głównej pamięci RAM będzie kosztować więcej czasu, niż gdyby wszystkie funkcje były wirtualne.

To jak za dawnych czasów, kiedy ludzie mówili, że programowanie strukturalne było powolne, ponieważ cały kod został podzielony na funkcje, każda funkcja wymagała alokacji stosu i wywołania funkcji!

Jedyny przypadek, w którym pomyślałbym o zawracaniu sobie głowy rozważaniem wpływu funkcji wirtualnej na wydajność, to sytuacja, gdy była ona bardzo intensywnie używana i tworzona w kodzie opartym na szablonach, który kończy się we wszystkim. Nawet wtedy nie poświęcałbym na to zbyt wiele wysiłku!

PS myślę o innych „łatwych w użyciu” językach - wszystkie ich metody są wirtualne pod osłonami i obecnie się nie indeksują.


4
Cóż, nawet dzisiaj unikanie wywołań funkcji jest ważne w przypadku aplikacji o wysokiej wydajności. Różnica polega na tym, że dzisiejsze kompilatory niezawodnie wbudowują małe funkcje, więc nie ponosimy konsekwencji związanych z szybkością pisania małych funkcji. Jeśli chodzi o funkcje wirtualne, inteligentne procesory mogą na nich przewidywać inteligentne gałęzie. Myślę, że to, że stare komputery były wolniejsze, nie jest problemem - tak, były znacznie wolniejsze, ale wtedy o tym wiedzieliśmy, więc daliśmy im znacznie mniejsze obciążenia. W 1992 roku wiedzieliśmy, że gdybyśmy zagrali MP3, musielibyśmy poświęcić na to zadanie ponad połowę procesora.
Qwertie

6
mp3 pochodzi z 1995 roku. W 92 roku mieliśmy zaledwie 386 plików, nie ma mowy, żeby mogli odtworzyć mp3, a 50% czasu procesora zakłada dobry wielozadaniowy system operacyjny, bezczynny proces i harmonogram z wywłaszczaniem. Żaden z nich nie istniał wówczas na rynku konsumenckim. to było 100% od momentu włączenia zasilania, koniec historii.
v.oddou,

7

Oprócz czasu wykonania istnieją inne kryteria wydajności. Vtable również zajmuje miejsce w pamięci, aw niektórych przypadkach można tego uniknąć: ATL używa „ symulowanego dynamicznego wiązania w czasie kompilacji” ” w z szablonamiuzyskać efekt „statycznego polimorfizmu”, który jest dość trudny do wyjaśnienia; w zasadzie przekazujesz klasę pochodną jako parametr do szablonu klasy bazowej, więc w czasie kompilacji klasa bazowa „wie”, jaka jest jej klasa pochodna w każdym wystąpieniu. Nie pozwoli ci przechowywać wielu różnych klas pochodnych w kolekcji typów podstawowych (jest to polimorfizm w czasie wykonywania), ale z punktu widzenia statycznego, jeśli chcesz utworzyć klasę Y, która jest taka sama jak wcześniej istniejąca klasa szablonu X, która ma punkty zaczepienia dla tego rodzaju nadpisywania, wystarczy przesłonić metody, na których Ci zależy, a następnie uzyskać podstawowe metody klasy X bez konieczności posiadania tabeli vtable.

W klasach z dużymi śladami pamięci koszt pojedynczego wskaźnika vtable jest niewielki, ale niektóre klasy ATL w COM są bardzo małe i warto zaoszczędzić na vtable, jeśli przypadek polimorfizmu w czasie wykonywania nigdy nie wystąpi.

Zobacz także to inne pytanie SO .

Przy okazji, oto post, który znalazłem, który mówi o aspektach wydajności czasu procesora.



4

Tak, masz rację i jeśli ciekawi Cię koszt wywołania funkcji wirtualnej, ten post może Cię zainteresować.


1
W artykule, do którego prowadzi łącze, nie uwzględniono bardzo ważnej części połączenia wirtualnego, a to może być błędne przewidywanie gałęzi.
Suma

4

Jedynym sposobem, w jaki widzę, że funkcja wirtualna stanie się problemem z wydajnością, jest wywołanie wielu funkcji wirtualnych w ścisłej pętli i wtedy i tylko wtedy, gdy gdy powodują błąd strony lub inną „ciężką” operację pamięci.

Chociaż, jak mówili inni, prawie nigdy nie będzie to dla ciebie problemem w prawdziwym życiu. A jeśli myślisz, że tak, uruchom program profilujący, przeprowadź testy i sprawdź, czy rzeczywiście stanowi to problem, zanim spróbujesz „cofnąć projekt” kodu w celu zwiększenia wydajności.


2
wywołanie czegokolwiek w ciasnej pętli prawdopodobnie utrzyma cały kod i dane w pamięci podręcznej ...
Greg Rogers

2
Tak, ale jeśli ta prawa pętla iteruje po liście obiektów, to każdy obiekt mógłby potencjalnie wywoływać funkcję wirtualną pod innym adresem za pomocą tego samego wywołania funkcji.
Daemin

3

Gdy metoda klasy nie jest wirtualna, kompilator zwykle wykonuje wbudowanie. Wręcz przeciwnie, gdy użyjesz wskaźnika do jakiejś klasy z funkcją wirtualną, prawdziwy adres będzie znany tylko w czasie wykonywania.

Dobrze obrazuje to test, różnica czasu ~ 700% (!):

#include <time.h>

class Direct
{
public:
    int Perform(int &ia) { return ++ia; }
};

class AbstrBase
{
public:
    virtual int Perform(int &ia)=0;
};

class Derived: public AbstrBase
{
public:
    virtual int Perform(int &ia) { return ++ia; }
};


int main(int argc, char* argv[])
{
    Direct *pdir, dir;
    pdir = &dir;

    int ia=0;
    double start = clock();
    while( pdir->Perform(ia) );
    double end = clock();
    printf( "Direct %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );

    Derived drv;
    AbstrBase *ab = &drv;

    ia=0;
    start = clock();
    while( ab->Perform(ia) );
    end = clock();
    printf( "Virtual: %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );

    return 0;
}

Wpływ wywołania funkcji wirtualnej w dużym stopniu zależy od sytuacji. Jeśli jest kilka wywołań i znaczna ilość pracy wewnątrz funkcji - może to być pomijalne.

Lub, gdy jest to połączenie wirtualne wielokrotnie używane, wykonując jakąś prostą operację - może być naprawdę duże.


4
Wywołanie funkcji wirtualnej jest drogie w porównaniu do ++ia. Więc co?
Bo Persson

2

Wracałem do tego co najmniej 20 razy w moim konkretnym projekcie. Chociaż mogą być pewne znaczne korzyści w zakresie ponownego wykorzystania kodu, przejrzystości, łatwości utrzymania i czytelności, z drugiej strony, hity wydajnościowe nadal skuteczne istnieć wspólnie z funkcji wirtualnych.

Czy hit wydajnościowy będzie zauważalny na nowoczesnym laptopie / komputerze stacjonarnym / tablecie ... prawdopodobnie nie! Jednak w niektórych przypadkach w przypadku systemów wbudowanych spadek wydajności może być czynnikiem powodującym nieefektywność kodu, zwłaszcza jeśli funkcja wirtualna jest wywoływana wielokrotnie w pętli.

Oto nieco przestarzały dokument, który przedstawia najlepsze praktyki dotyczące języka C / C ++ w kontekście systemów wbudowanych: http://www.open-std.org/jtc1/sc22/wg21/docs/ESC_Boston_01_304_paper.pdf

Podsumowując: to od programisty zależy zrozumienie zalet / wad używania określonej konstrukcji zamiast innej. O ile nie jesteś nastawiony na super wydajność, prawdopodobnie nie przejmujesz się spadkiem wydajności i powinieneś używać wszystkich fajnych rzeczy OO w C ++, aby uczynić swój kod tak użytecznym, jak to tylko możliwe.


2

Z mojego doświadczenia wynika, że ​​najważniejszą rzeczą jest możliwość wbudowania funkcji. Jeśli masz potrzeby w zakresie wydajności / optymalizacji, które nakazują, aby funkcja była wbudowana, nie możesz uczynić funkcji wirtualną, ponieważ by temu zapobiec. W przeciwnym razie prawdopodobnie nie zauważysz różnicy.


1

Należy zauważyć, że:

boolean contains(A element) {
    for (A current: this)
        if (element.equals(current))
            return true;
    return false;
}

może być szybsze niż to:

boolean contains(A element) {
    for (A current: this)
        if (current.equals(equals))
            return true;
    return false;
}

Dzieje się tak, ponieważ pierwsza metoda wywołuje tylko jedną funkcję, podczas gdy druga może wywoływać wiele różnych funkcji. Dotyczy to wszelkich funkcji wirtualnych w dowolnym języku.

Mówię „może”, ponieważ to zależy od kompilatora, pamięci podręcznej itp.


0

Spadek wydajności wynikający z używania funkcji wirtualnych nigdy nie przeważy korzyści uzyskanych na poziomie projektowania. Przypuszczalnie wywołanie funkcji wirtualnej byłoby o 25% mniej wydajne niż bezpośrednie wywołanie funkcji statycznej. Dzieje się tak, ponieważ w VMT istnieje pewien poziom pośredni. Jednak czas potrzebny do wykonania połączenia jest zwykle bardzo krótki w porównaniu z czasem potrzebnym na rzeczywiste wykonanie funkcji, więc całkowity koszt wydajności będzie niewielki, szczególnie przy obecnej wydajności sprzętu. Ponadto kompilator może czasami zoptymalizować i zobaczyć, że nie jest potrzebne żadne wirtualne wywołanie i skompilować je do statycznego wywołania. Więc nie martw się, używaj funkcji wirtualnych i klas abstrakcyjnych tak często, jak potrzebujesz.


2
nigdy przenigdy, nieważne jak mały jest docelowy komputer?
zumalifeguard

Mogłabym się zgodzić, gdybyś sformułował to następująco: The performance penalty of using virtual functions can sometimes be so insignificant that it is completely outweighed by the advantages you get at the design level.Kluczowa różnica polega na tym, że sometimesnie never.
underscore_d

-1

Zawsze to kwestionowałem, zwłaszcza że - kilka lat temu - przeprowadziłem również taki test porównując czasy wywołania standardowej metody składowej z wirtualną i byłem bardzo zły z powodu wyników w tamtym czasie, mając puste wirtualne połączenia 8 razy wolniej niż nie-wirtualne.

Dzisiaj musiałem zdecydować, czy użyć funkcji wirtualnej do przydzielenia większej ilości pamięci w mojej klasie bufora, w aplikacji o bardzo krytycznym znaczeniu dla wydajności, więc wyszukałem go w Google (i znalazłem) i na koniec ponownie przeprowadziłem test.

// g++ -std=c++0x -o perf perf.cpp -lrt
#include <typeinfo>    // typeid
#include <cstdio>      // printf
#include <cstdlib>     // atoll
#include <ctime>       // clock_gettime

struct Virtual { virtual int call() { return 42; } }; 
struct Inline { inline int call() { return 42; } }; 
struct Normal { int call(); };
int Normal::call() { return 42; }

template<typename T>
void test(unsigned long long count) {
    std::printf("Timing function calls of '%s' %llu times ...\n", typeid(T).name(), count);

    timespec t0, t1;
    clock_gettime(CLOCK_REALTIME, &t0);

    T test;
    while (count--) test.call();

    clock_gettime(CLOCK_REALTIME, &t1);
    t1.tv_sec -= t0.tv_sec;
    t1.tv_nsec = t1.tv_nsec > t0.tv_nsec
        ? t1.tv_nsec - t0.tv_nsec
        : 1000000000lu - t0.tv_nsec;

    std::printf(" -- result: %d sec %ld nsec\n", t1.tv_sec, t1.tv_nsec);
}

template<typename T, typename Ua, typename... Un>
void test(unsigned long long count) {
    test<T>(count);
    test<Ua, Un...>(count);
}

int main(int argc, const char* argv[]) {
    test<Inline, Normal, Virtual>(argc == 2 ? atoll(argv[1]) : 10000000000llu);
    return 0;
}

I był naprawdę zaskoczony, że to - w rzeczywistości - naprawdę nie ma już żadnego znaczenia. Chociaż sensowne jest posiadanie inline szybciej niż nie-wirtualne i że są one szybsze niż wirtualne, często wiąże się to z ogólnym obciążeniem komputera, czy pamięć podręczna zawiera niezbędne dane, czy nie, i chociaż możesz być w stanie zoptymalizować Myślę, że na poziomie pamięci podręcznej powinni to robić programiści kompilatorów, a nie twórcy aplikacji.


12
Myślę, że jest całkiem prawdopodobne, że Twój kompilator może powiedzieć, że wywołanie funkcji wirtualnej w kodzie może wywołać tylko Virtual :: call. W takim przypadku może go po prostu wstawić. Nic też nie stoi na przeszkodzie, aby kompilator włączył Normal :: call, nawet jeśli o to nie prosiłeś. Myślę więc, że jest całkiem możliwe, że uzyskasz te same czasy dla 3 operacji, ponieważ kompilator generuje dla nich identyczny kod.
Bjarke H. Roune
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.