2013-07-09 10 views
13

Niedawno podczas dyskusji zostałem poproszony przez innego programistę o wprowadzenie pewnych zmian w kodzie. Miałem coś takiego:Co to jest fast string.empty() lub string.size() == 0?

if(mystring.size() == 0) 
    // do something 
else 
    // do something else 

Dyskusję dotyczącą używania mystring.empty() do sprawdzania, czy ciąg jest pusty. Teraz zgadzam się, że można argumentować, że kod string.empty() jest bardziej szczegółowy i czytelny, ale czy są jakieś korzyści z wydajności?

zrobiłem kilka kopanie i znaleźć te 2 odpowiedzi odnoszące się do mojego pytania:

Obie odpowiedzi podporą moją tezę, że string.empty() jest po prostu bardziej czytelne i nie oferuje żadnych korzyści związanych z wydajnością w porównaniu z string.size() == 0.

Nadal chcę mieć pewność, czy istnieją jakieś implementacje string, które utrzymują wewnętrzną flagę boolowatą w celu sprawdzenia, czy ciąg znaków jest pusty, czy nie?

Czy istnieją inne sposoby wykorzystania niektórych implementacji, które unieważniają moje roszczenia?

+0

@chris: Zgadzam się z tobą kolega. Chciałem tylko potwierdzić. –

+13

"moje twierdzenie, że string.empty() jest po prostu bardziej czytelny i nie oferuje żadnych korzyści wydajnościowych" - ** po prostu? ** Czytelność jest pierwszym priorytetem właściwego programowania; występ, szczególnie na tym poziomie, daleko za sobą. "jeśli są jakieś implementacje ciągu znaków" - nawet jeśli byłyby, gdyby były tylko porównanie wartości do zera, tak samo jak dla rozmiaru == 0. –

+0

@JimBalter: Uważam również, że najważniejsza jest czytelność kodu, I po prostu chciał uzyskać dane wejściowe od ludzi tutaj na SO jest podstawowe implementacje empty() były szybsze niż size()? –

Odpowiedz

25

standard określa empty() tak:

BOOL pusty() const noexcept;
Powroty: wielkość() == 0.

byłbyś do muru, aby znaleźć coś, co nie robi, a wszelkie różnice wydajności będzie znikomy ze względu na obu operacji będących stałym czasie . Oczekuję, że oba będą kompilowały do ​​tego samego zestawu przy każdej rozsądnej implementacji.

To powiedziawszy, empty() jest jasne i jednoznaczne. Powinieneś preferować to ponad size() == 0 (lub !size()) dla czytelności.

+0

Zgadzam się, że puste() jest bardziej przejrzyste. Jednak w niektórych przypadkach istnieje różnica między size() i empty(). Testowany z msvc 2015: Dla std :: string empty() robi size() == 0, a string :: size() zwraca rozmiar w pamięci podręcznej (więc nie ma podziału). Ale dla wektora :: empty() robi pierwszy() == last() i wektor :: size() wykonuje odejmowanie z niejawnym podziałem (!) - w tym konkretnym przypadku z bitshift. Tak więc w przypadku wektora, empty() robi tylko porównanie i unika tego podziału. Realistycznie, w większości przypadków nie spowoduje to żadnej zauważalnej różnicy :) – kalmiya

+1

@kalmiya, Interesujące, zauważam, że Clang generuje [prawie identyczny] (https://godbolt.org/g/821yyw) montaż. – chris

7

Teraz, to jest całkiem banalna sprawa, ale postaram się przykryć wyczerpująco więc cokolwiek argumenty są wprowadzane przez współpracowników nie mogą cię zaskoczyć ....

Jak zwykle, jeśli profilowanie sprawiło, że naprawdę musiałeś się martwić , mierzyć: tam może być różnica między (patrz poniżej). Ale w ogólnym kodem przeglądu sytuacji dla nie-okazał-problemowo-powolnego kodu, nierozwiązane kwestie to:

  • w niektórych innych pojemników (np C++ 03 list, ale nie C++ 11), był size() jest mniej wydajny niż empty(), co prowadzi do pewnych wskazówek dotyczących kodowania, które preferują generalnie empty() przez size(), tak więc jeśli ktoś musiałby później zmienić pojemnik (lub uogólnić przetwarzanie na szablon, w którym typ pojemnika może się zmieniać), nie trzeba wprowadzać żadnych zmian w celu zachowania wydajność.

  • czy to odzwierciedla bardziej naturalny sposób pojmowania testu? - nie tylko to, co zdarzyło Ci się najpierw pomyśleć, czy size(), ponieważ nie jesteś przyzwyczajony do używania empty(), ale kiedy jesteś w 100% skoncentrowany na otaczającej logice kodu, czy size() lub empty() pasuje lepiej? Na przykład, być może dlatego, że jest to jeden z kilku testów size() i lubisz mieć konsystencję, lub ponieważ implementujesz słynny algorytm lub formułę tradycyjnie wyrażaną pod względem wielkości: bycie konsekwentnym może zmniejszyć hałas/wysiłek w sprawdzaniu implementacji wbrew formule.

Większość czasu powyższe czynniki są nieznaczne, a podnosząc kwestię w swojej recenzji kodu jest naprawdę strata czasu.

Możliwa różnica wydajności

Chociaż standard wymaga równoważności funkcjonalnej, niektóre implementacje może je realizować w różny sposób, choć z trudem, a ja do tej pory nie udało się udokumentować szczególnie prawdopodobną przyczynę tego.

C++ 11 ma więcej ograniczeń niż C++ 03 nad zachowaniami innych funkcji, które wybory wdrożeniowe wpływ: data() musi być zakończona NUL (kiedyś tylko c_str()) [size()] jest teraz ważny wskaźnik i należy zwrócić odwołanie do postaci NUL. Z różnych subtelnych powodów, ograniczenia te sprawiają, że jeszcze bardziej prawdopodobne jest, że empty() nie będzie szybszy niż size().

W każdym razie - miara, jeśli musisz się troszczyć.

+2

Jeśli twoje "puste" miałyby sprawdzić NUL na początku łańcucha, nie byłoby to poprawne, ponieważ 'std :: string' zezwala na znaki' NUL', w przeciwieństwie do łańcuchów C. – rici

+0

W C++ 11 zawartość ciągu znaków musi być zakończona znakiem NUL. Ale @rici ma rację; implementacja, która sprawdzała pierwszy znak dla NUL, zostałaby zerwana. –

+0

Myślę, że oprócz kwestii nul jest tu sporo dobrych punktów.Z łatwością widzę, że opcja 'size()' działa lepiej w tym kontekście, a punkt dotyczący wymogów złożoności zmieniających się w zależności od kontenera również jest dobry. – chris