Ile kosztuje narzut inteligentnych wskaźników w porównaniu do zwykłych wskaźników w C ++ 11? Innymi słowy, czy mój kod będzie wolniejszy, jeśli użyję inteligentnych wskaźników, a jeśli tak, to o ile wolniej?
W szczególności pytam o C ++ 11 std::shared_ptr
i std::unique_ptr
.
Oczywiście rzeczy zepchnięte w dół będą większe (przynajmniej tak mi się wydaje), ponieważ inteligentny wskaźnik musi również przechowywać swój stan wewnętrzny (liczbę referencji itp.), Pytanie naprawdę brzmi, ile to będzie wpłynąć na moje wyniki, jeśli w ogóle?
Na przykład zwracam inteligentny wskaźnik z funkcji zamiast normalnego wskaźnika:
std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();
Lub, na przykład, gdy jedna z moich funkcji akceptuje inteligentny wskaźnik jako parametr zamiast zwykłego wskaźnika:
void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);
c++
performance
c++11
smart-pointers
Venemo
źródło
źródło
std::unique_ptr
czystd::shared_ptr
?Odpowiedzi:
std::unique_ptr
ma narzut pamięci tylko wtedy, gdy dostarczysz mu jakiś nietrywialny element usuwający.std::shared_ptr
zawsze ma narzut pamięci dla licznika odniesienia, chociaż jest bardzo mały.std::unique_ptr
ma narzut czasowy tylko podczas konstruktora (jeśli musi skopiować podany deleter i / lub null-zainicjować wskaźnik) i podczas destruktora (aby zniszczyć posiadany obiekt).std::shared_ptr
ma narzut czasowy w konstruktorze (aby utworzyć licznik odniesienia), w destruktorze (aby zmniejszyć licznik odniesienia i ewentualnie zniszczyć obiekt) oraz w operatorze przypisania (aby zwiększyć licznik odniesienia). Ze względu na gwarancje bezpieczeństwa wątkówstd::shared_ptr
te przyrosty / ubytki są niepodzielne, co zwiększa obciążenie.Zauważ, że żadna z nich nie ma nadmiernego czasu związanego z dereferencją (w uzyskaniu odniesienia do posiadanego obiektu), podczas gdy ta operacja wydaje się być najbardziej powszechna dla wskaźników.
Podsumowując, jest pewien narzut, ale nie powinien spowalniać kodu, chyba że ciągle tworzysz i niszczysz inteligentne wskaźniki.
źródło
unique_ptr
nie ma narzutu w destruktorze. Robi dokładnie to samo, co w przypadku surowego wskaźnika.std::unique_ptr
? Jeśli skonstruujesz astd::unique_ptr<int>
, wewnętrznyint*
zostanie zainicjalizowany,nullptr
czy ci się to podoba, czy nie.Podobnie jak w przypadku wszystkich wydajności kodu, jedynym naprawdę niezawodnym sposobem uzyskania twardych informacji jest pomiar i / lub kontrola kodu maszynowego.
To powiedziawszy, mówi to proste rozumowanie
Możesz spodziewać się trochę narzutu w kompilacjach debugowania, ponieważ np.
operator->
Musi być wykonywany jako wywołanie funkcji, abyś mógł do niego wejść (jest to z kolei z powodu ogólnego braku wsparcia dla oznaczania klas i funkcji jako niedebugowanych).Ponieważ
shared_ptr
możesz spodziewać się pewnego narzutu podczas początkowego tworzenia, ponieważ wiąże się to z dynamiczną alokacją bloku sterującego, a alokacja dynamiczna jest znacznie wolniejsza niż jakakolwiek inna podstawowa operacja w C ++ (używaj,make_shared
gdy jest to praktycznie możliwe, aby zminimalizować ten narzut).Również ponieważ
shared_ptr
istnieje pewien minimalny narzut związany z utrzymaniem liczby referencji, np. Podczas przekazywaniashared_ptr
wartości przez wartość, ale nie ma takiego narzutu w przypadkuunique_ptr
.Mając na uwadze pierwszy punkt powyżej, mierząc, rób to zarówno dla kompilacji debugowania, jak i wydania.
Międzynarodowy komitet normalizacyjny C ++ opublikował raport techniczny dotyczący wydajności , ale był to rok 2006, wcześniej
unique_ptr
ishared_ptr
został dodany do biblioteki standardowej. Mimo to inteligentne wskazówki były wtedy starym kapeluszem, więc raport uwzględniał również to. Cytując odpowiednią część:Zgodnie z przemyślanym przypuszczeniem, „zgodność ze stanem techniki” została osiągnięta dzięki najpopularniejszym obecnie kompilatorom, począwszy od początku 2014 r.
źródło
Moja odpowiedź różni się od innych i naprawdę zastanawiam się, czy kiedykolwiek sprofilowali kod.
shared_ptr ma znaczny narzut związany z tworzeniem z powodu alokacji pamięci dla bloku kontrolnego (który utrzymuje licznik ref i listę wskaźników do wszystkich słabych referencji). Ma również ogromne narzuty pamięci z powodu tego i faktu, że std :: shared_ptr jest zawsze krotką z dwoma wskaźnikami (jeden do obiektu, jeden do bloku sterującego).
Jeśli przekażesz shared_pointer do funkcji jako parametr wartości, będzie on co najmniej 10 razy wolniejszy niż normalne wywołanie i utworzy wiele kodów w segmencie kodu do rozwijania stosu. Jeśli przejdziesz to przez odniesienie, otrzymasz dodatkowe pośrednictwo, które może być również znacznie gorsze pod względem wydajności.
Dlatego nie powinieneś tego robić, chyba że funkcja jest naprawdę zaangażowana w zarządzanie własnością. W przeciwnym razie użyj „shared_ptr.get ()”. Nie jest zaprojektowany, aby upewnić się, że obiekt nie zostanie zabity podczas normalnego wywołania funkcji.
Jeśli oszalejesz i użyjesz shared_ptr na małych obiektach, takich jak abstrakcyjne drzewo składni w kompilatorze lub na małych węzłach w dowolnej innej strukturze wykresu, zobaczysz ogromny spadek wydajności i ogromny wzrost pamięci. Widziałem system parsera, który został przepisany wkrótce po wejściu C ++ 14 na rynek i zanim programista nauczył się poprawnie używać inteligentnych wskaźników. Przepisanie było o wielkość wolniejsze niż stary kod.
To nie jest srebrna kula, a surowe wskaźniki też nie są złe z definicji. Źli programiści są źli, a zły projekt jest zły. Projektuj ostrożnie, projektuj mając na uwadze jasne prawa własności i staraj się używać shared_ptr głównie na granicy API podsystemu.
Jeśli chcesz dowiedzieć się więcej, możesz obejrzeć dobrą rozmowę Nicolai M. Josuttisa o „Rzeczywistej cenie współdzielonych wskaźników w C ++” https://vimeo.com/131189627 Zagłębia
się w szczegóły implementacji i architekturę procesora dla barier zapisu, atomowej zamki itp. po wysłuchaniu nigdy nie powiesz, że ta funkcja jest tania. Jeśli chcesz mieć dowód wolniejszej wielkości, pomiń pierwsze 48 minut i zobacz, jak uruchamia przykładowy kod, który działa do 180 razy wolniej (skompilowany z -O3), gdy wszędzie używa wskaźnika współdzielonego.
źródło
std::make_shared()
? Ponadto uważam, że demonstracje rażącego nadużycia są trochę nudne ...Innymi słowy, czy mój kod będzie wolniejszy, jeśli użyję inteligentnych wskaźników, a jeśli tak, to o ile wolniej?
Wolniej? Najprawdopodobniej nie, chyba że tworzysz ogromny indeks za pomocą shared_ptrs i nie masz wystarczającej ilości pamięci do tego stopnia, że komputer zaczyna marszczyć się, jak starsza pani spadająca na ziemię z daleka przez nieznośną siłę.
To, co spowolniłoby Twój kod, to powolne wyszukiwania, niepotrzebne przetwarzanie w pętli, ogromne kopie danych i wiele operacji zapisu na dysku (np. Setki).
Wszystkie zalety inteligentnego wskaźnika są związane z zarządzaniem. Ale czy koszty ogólne są konieczne? To zależy od implementacji. Powiedzmy, że wykonujesz iterację po tablicy 3 faz, z których każda ma tablicę 1024 elementów. Tworzenie
Ale czy naprawdę chcesz to zrobić?smart_ptr
dla tego procesu może być przesadą, ponieważ po zakończeniu iteracji będziesz wiedział, że musisz go usunąć. Dzięki temu możesz zyskać dodatkową pamięć, nie używającsmart_ptr
...Pojedynczy wyciek pamięci może spowodować, że twój produkt będzie miał punkt awarii w czasie (powiedzmy, że twój program wycieka 4 megabajty na godzinę, uszkodzenie komputera zajęłoby miesiące, niemniej jednak zepsuje się, wiesz o tym, ponieważ wyciek tam jest) .
To tak, jakby powiedzieć „Twoje oprogramowanie jest objęte gwarancją przez 3 miesiące, więc zadzwoń do mnie po serwis”.
Więc ostatecznie to naprawdę kwestia ... czy poradzisz sobie z tym ryzykiem? czy używanie surowego wskaźnika do obsługi indeksowania setek różnych obiektów jest warte utraty kontroli nad pamięcią.
Jeśli odpowiedź brzmi tak, użyj surowego wskaźnika.
Jeśli nawet nie chcesz się nad tym zastanawiać,
smart_ptr
jest to dobre, realne i niesamowite rozwiązanie.źródło
smart_ptr
są naprawdę przydatne dla dużych zespołówThats why you should not do this unless the function is really involved in ownership management
... świetna odpowiedź, dziękuję, głosowanoDla uogólnienia i tylko dla
[]
operatora jest ~ 5X wolniejszy niż surowy wskaźnik, jak pokazano w poniższym kodzie, który został skompilowany przy użyciugcc -lstdc++ -std=c++14 -O0
i wyprowadził następujący wynik:Zaczynam się uczyć c ++, mam to na myśli: zawsze musisz wiedzieć, co robisz i poświęcić więcej czasu, aby dowiedzieć się, co inni zrobili w twoim c ++.
EDYTOWAĆ
Zgodnie z zaleceniem @Mohan Kumar podałem więcej szczegółów. Wersja gcc to
7.4.0 (Ubuntu 7.4.0-1ubuntu1~14.04~ppa1)
, Powyższy wynik został uzyskany-O0
, gdy używam flagi '-O2', otrzymałem to:Następnie przesunięte do
clang version 3.9.0
,-O0
było:-O2
był:Rezultat brzęku
-O2
jest niesamowity.źródło
-O0
testuj kodu ani nie debuguj go. Wynik będzie bardzo nieefektywny . Zawsze należy stosować co najmniej-O2
(albo-O3
w dzisiejszych czasach, ponieważ niektóre wektoryzacja nie są wykonywane w-O2
)free
wywołanie w teście malloc idelete[]
dla new (lub uczynić zmiennąa
statyczną), ponieważunique_ptr
s wołajądelete[]
pod maską, w swoich destruktorach.