2012-05-30 16 views
28

Czytanie różnych pytań tutaj na stosie   Przepełnienie wokół iteratorów i wydajności C++ **, zacząłem się zastanawiać, czy for(auto& elem : container) zostaje "rozszerzony" przez kompilator do najlepszej możliwej wersji? (Rodzaj podobnego do auto, który kompilator natychmiast wstawia do właściwego typu i dlatego nigdy nie jest wolniejszy, a czasami szybszy).Czy pętla ranged dla pętli jest korzystna dla wydajności?

** Na przykład, czy to ma znaczenie, jeśli piszesz

for(iterator it = container.begin(), eit = container.end(); it != eit; ++it) 

lub

for(iterator it = container.begin(); it != container.end(); ++it) 

dla pojemników niewyspecjalizowanych unieważniania?

+1

W jaki sposób użycie opcji auto zamiast typedefs pomaga uzyskać lepszą wydajność? –

+6

szybciej lub nie, jest dziesięć razy bardziej czytelny. – inf

+0

@ JonathanWakely Przepraszamy, wspomnienie o typedefs było błędem z mojej strony. Nie mogę znaleźć odpowiedniego odnośnika, ale to, co Stephen T. Lavavej ma na punkcie 49: 30+ w channel9.msdn.com/Events/GoingNative/GoingNative-2012/STL11-Magic-Secrets – TeaOverflow

Odpowiedz

24

Standard jest twoim przyjacielem, zobacz [ stmt.ranged]/1

Dla zakresu opartego na zestawieniu formularza

for (for-range-declaration : expression) statement 

let zakres-Init jest równoznaczne z wyrażeniem otoczony nawiasami

(expression) 

i zakres oparte na zestawieniu postaci

for (for-range-declaration : braced-init-list) statement 

let zakres-Init za równoważne do listy wzmocnionych-init. W każdym przypadku, zakres oparte for stwierdzenie jest równoznaczne z

{ 
    auto && __range = range-init; 
    for (auto __begin = begin-expr, 
      __end = end-expr; 
     __begin != __end; 
     ++__begin) 
    { 
    for-range-declaration = *__begin; 
    statement 
    } 
} 

Więc tak, gwarancje standardowe że najlepszą możliwą formą jest osiągnięty.

Dla wielu kontenerów, takich jak vector, nieokreślonym zachowaniem jest modyfikowanie (wstawianie/usuwanie) podczas tej iteracji.

+14

co oznacza skrót UB? – TeaOverflow

+9

+1 dla wyjaśnienia opartego na specyfikacji. @ Evgeni Undefined Behavior – KillianDS

+0

@KillianDS Dzięki. Ale to brzmi ...niepokojący – TeaOverflow

5

Nie. To samo, co stara pętla for z iteratorami. W końcu oparty na zakresie for współpracuje z iteratorami wewnętrznie. Kompilator po prostu tworzy równoważny kod dla obu.

+0

Wiedziałem, że używa iteratory, ale co lubi wiedzieć, czy kompilator tworzy najlepszą możliwą wersję pętli z iteratorami ... – TeaOverflow

23

piecyk do to tak szybko, jak to możliwe, ponieważ buforuje iterator end [citation provided], wykorzystuje preinkrementuj i tylko dereferences iteracyjnej raz.

więc jeśli masz tendencję do pisania:

for(iterator i = cont.begin(); i != cont.end(); i++) { /**/ } 

Następnie, tak, gama-for może być nieznacznie szybciej, ponieważ jest to również łatwiej napisać nie ma powodu, aby nie używać (w razie potrzeby).

N.B. Powiedziałem, że jest tak szybki, jak to możliwe, nie jest jednak szybszy niż to możliwe. Możesz osiągnąć dokładnie taką samą wydajność, jeśli piszesz ręcznie swoje pętle.

19

Z ciekawości postanowiłem spojrzeć na kod montażowej dla obu podejść:

int foo1(const std::vector<int>& v) { 
    int res = 0; 
    for (auto x : v) 
     res += x; 
    return res; 
} 

int foo2(const std::vector<int>& v) { 
    int res = 0; 
    for (std::vector<int>::const_iterator it = v.begin(); it != v.end(); ++it) 
     res += *it; 
    return res; 
} 

a kod montażowej (z -O3 i gcc 4.6) jest dokładnie taka sama dla obu podejść (kod dla foo2 jest pominięty, ponieważ jest dokładnie taki sam):

080486d4 <foo1(std::vector<int, std::allocator<int> > const&)>: 
80486d4:  8b 44 24 04    mov 0x4(%esp),%eax 
80486d8:  8b 10     mov (%eax),%edx 
80486da:  8b 48 04    mov 0x4(%eax),%ecx 
80486dd:  b8 00 00 00 00   mov $0x0,%eax 
80486e2:  39 ca     cmp %ecx,%edx 
80486e4:  74 09     je  80486ef <foo1(std::vector<int, std::allocator<int> > const&)+0x1b> 
80486e6:  03 02     add (%edx),%eax 
80486e8:  83 c2 04    add $0x4,%edx 
80486eb:  39 d1     cmp %edx,%ecx 
80486ed:  75 f7     jne 80486e6 <foo1(std::vector<int, std::allocator<int> > const&)+0x12> 
80486ef:  f3 c3     repz ret 

Tak, tak, oba podejścia są takie same.

UPDATE: To samo spostrzeżenie odnosi się do innych pojemników (lub typów elementów), takich jak vector<string> i map<string, string>. W takich przypadkach szczególnie ważne jest użycie odniesienia w pętli dystansowej. W przeciwnym razie tworzony jest tymczasowy kod i pojawia się dużo dodatkowego kodu (w poprzednich przykładach nie było potrzebne, ponieważ wartości vector zawierały tylko wartości vector).

Dla przypadku map<string, string> C++ fragmencie kodu jest stosowany:

int foo1(const std::map<std::string, std::string>& v) { 
    int res = 0; 
    for (const auto& x : v) { 
     res += (x.first.size() + x.second.size()); 
    } 
    return res; 
} 

int foo2(const std::map<std::string, std::string>& v) { 
    int res = 0; 
    for (auto it = v.begin(), end = v.end(); it != end; ++it) { 
     res += (it->first.size() + it->second.size()); 
    } 
    return res; 
} 

I kod montaż (dla obu przypadkach) jest:

8048d70:  56      push %esi 
8048d71:  53      push %ebx 
8048d72:  31 db     xor %ebx,%ebx 
8048d74:  83 ec 14    sub $0x14,%esp 
8048d77:  8b 74 24 20    mov 0x20(%esp),%esi 
8048d7b:  8b 46 0c    mov 0xc(%esi),%eax 
8048d7e:  83 c6 04    add $0x4,%esi 
8048d81:  39 f0     cmp %esi,%eax 
8048d83:  74 1b     je  8048da0 
8048d85:  8d 76 00    lea 0x0(%esi),%esi 
8048d88:  8b 50 10    mov 0x10(%eax),%edx 
8048d8b:  03 5a f4    add -0xc(%edx),%ebx 
8048d8e:  8b 50 14    mov 0x14(%eax),%edx 
8048d91:  03 5a f4    add -0xc(%edx),%ebx 
8048d94:  89 04 24    mov %eax,(%esp) 
8048d97:  e8 f4 fb ff ff   call 8048990 <std::_Rb_tree_increment(std::_Rb_tree_node_base const*)@plt> 
8048d9c:  39 c6     cmp %eax,%esi 
8048d9e:  75 e8     jne 8048d88 
8048da0:  83 c4 14    add $0x14,%esp 
8048da3:  89 d8     mov %ebx,%eax 
8048da5:  5b      pop %ebx 
8048da6:  5e      pop %esi 
8048da7:  c3      ret  
+11

Zoptymalizowany kompilator 'v.end()' w tym przypadku, nie zawsze może być w stanie to zrobić (dla innych pojemniki). – Motti

+1

Tak jak @Motti mówi, to nie jest dowód. – ergosys

+1

OP zawiera również opcję buforowania 'v.end()', a kod zespołu nadal wyglądałby tak samo. Jeśli czujesz się lepiej z tym ... – betabandido

3

Jest to prawdopodobnie szybsze, w rzadkich przypadkach. Ponieważ nie można nazwać iteratora, optymalizator może łatwiej dowieść, że twoja pętla nie może modyfikować iteratora. Wpływa na to np. optymalizacje rozwijania pętli.

Powiązane problemy