Czy nowoczesny C++ da ci wydajność za darmo?
Czasami twierdzi się, że C++11/14 może zwiększyć wydajność nawet podczas kompilacji kodu C++98. Uzasadnienie jest zwykle zgodne z semantyką move, ponieważ w niektórych przypadkach konstruktory rvalue są generowane automatycznie lub są teraz częścią STL. Teraz zastanawiam się, czy te sprawy były wcześniej faktycznie już obsługiwane przez RVO lub podobnych optymalizacji kompilatora.
Moje pytanie brzmi, czy mógłbyś podać mi przykład fragmentu kodu C++98, który, bez modyfikacji działa szybciej przy użyciu kompilatora obsługującego nowe funkcje języka. Rozumiem, że standardowy kompilator nie jest wymagany do kopiowania i właśnie z tego powodu semantyka move może przynieść szybkość, ale chciałbym zobaczyć mniej patologiczny przypadek, jeśli wolisz.
EDIT: dla jasności, nie pytam, czy nowe Kompilatory są szybsze od starych, ale raczej, jeśli jest kod, w którym dodanie -std = c++14 do flag kompilatora byłoby uruchamiane szybciej (unikaj kopiowania, ale jeśli możesz wymyślić coś jeszcze poza semantyką move, też byłbym zainteresowany)
2 answers
Znam 5 ogólnych kategorii, w których przekompilowanie kompilatora C++03 jako C++11 może spowodować nieograniczony wzrost wydajności, który jest praktycznie niezwiązany z jakością implementacji. Są to wszystkie odmiany semantyki ruchu.
std::vector
reallocate
struct bar{
std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03
Za każdym razem, gdy bufor foo
jest realokowany w C++03, kopiuje on co vector
w bar
.
W C++11 zamiast tego przesuwa bar::data
s, który jest w zasadzie wolny.
W tym przypadku polega to na optymalizacje wewnątrz std
kontenera vector
. W każdym przypadku poniżej, użycie kontenerów std
jest tylko dlatego, że są to obiekty C++, które mają wydajną semantykę move
W C++11 "automatycznie" podczas aktualizacji kompilatora. Obiekty, które go nie blokują, które zawierają kontener std
również dziedziczą automatyczne ulepszone konstruktory move
.
Awaria NRVO
Gdy NRVO (named return value optimization) zawiedzie, w C++03 spada z powrotem na kopię, w C++11 spada z powrotem na ruszaj się. Awarie NRVO są łatwe:
std::vector<int> foo(int count){
std::vector<int> v; // oops
if (count<=0) return std::vector<int>();
v.reserve(count);
for(int i=0;i<count;++i)
v.push_back(i);
return v;
}
Lub nawet:
std::vector<int> foo(bool which) {
std::vector<int> a, b;
// do work, filling a and b, using the other for calculations
if (which)
return a;
else
return b;
}
Mamy trzy wartości -- wartość zwracaną i dwie różne wartości w funkcji. Elision pozwala wartości wewnątrz funkcji być "scalone" z wartością zwracaną, ale nie ze sobą. Oba nie mogą być połączone z wartością zwracaną bez scalania się ze sobą.
Podstawowym problemem jest to, że nrvo jest kruche, a kod ze zmianami Nie w pobliżu return
witryny może nagle mieć ogromne zmniejszenie wydajności w tym miejscu bez emisji diagnostycznej. C++11 kończy się move
, Podczas Gdy C++03 kończy się kopią.
Zwracanie argumentu funkcji
Tutaj też nie da się uciec:
std::set<int> func(std::set<int> in){
return in;
}
W C++11 jest to tanie: w C++03 nie ma sposobu na uniknięcie kopii. Argumenty funkcji nie mogą zostać uzupełnione o zwracaną wartość, ponieważ żywotność i lokalizacja parametru oraz zwracanej wartości jest zarządzana przez wywołanie kod.
Jednakże, C++11 może przenosić się z jednego do drugiego. (W mniej zabawkowym przykładzie można coś zrobić z set
).
push_back
lub insert
Ostatecznie usunięcie kontenerów nie następuje: ale C++11 przeciąża operatory rvalue move insert, które zapisują kopie.
struct whatever {
std::string data;
int count;
whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );
W C++03 tworzony jest tymczasowy whatever
, a następnie kopiowany do wektora v
. 2 std::string
są przydzielane bufory, każdy z identycznymi danymi, a jeden jest odrzucany.
W C++11 tworzony jest tymczasowy whatever
. Na whatever&&
push_back
przeciążenie wtedy move
S, że tymczasowe do wektora v
. Jeden bufor std::string
jest alokowany i przenoszony do wektora. Pusty std::string
jest odrzucany.
Przypisanie
Skradzione z odpowiedzi @Jarod42 poniżej.
Elision nie może wystąpić z przypisaniem, ale przenieść-z can.
std::set<int> some_function();
std::set<int> some_value;
// code
some_value = some_function();
Tutaj some_function
zwraca kandydata, z którego można uciec, ale ponieważ nie jest on używany do bezpośredniej budowy obiektu, nie może być / align = "left" / W C++03 powyższe powoduje skopiowanie zawartości tymczasowej do some_value
. W C++11 jest przenoszony do some_value
, który w zasadzie jest wolny.
Aby uzyskać pełny efekt powyższego, potrzebujesz kompilatora, który syntetyzuje konstruktory ruchu i przypisania dla Ciebie.
MSVC 2013 implementuje konstruktory ruchu w kontenerach std
, ale nie syntetyzuje konstruktorów ruchu na typach.
Więc typy zawierające std::vector
s i podobne nie otrzymują takich ulepszenia w MSVC2013, ale zacznie je otrzymywać w MSVC2015.
Clang i gcc już dawno wprowadziły Ukryte konstruktory ruchu. Kompilator Intela z 2013 roku będzie obsługiwał domyślne generowanie konstruktorów move, jeśli przejdziesz -Qoption,cpp,--gen_move_operations
(nie robią tego domyślnie, aby być kompatybilnym krzyżowo z MSVC2013).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-17 15:40:16
Jeśli masz coś takiego:
std::vector<int> foo(); // function declaration.
std::vector<int> v;
// some code
v = foo();
Masz kopię w C++03, podczas gdy masz zadanie move W C++11. więc masz darmową optymalizację w tym przypadku.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-22 04:50:36