Wydajność w pętlach vs. iteratorach
Aby zdecydować, czy użyć pętli, czy iteratorów, musisz wiedzieć, która implementacja jest szybsza: wersja funkcji search z jawną pętlą for czy wersja z iteratorami.
Przeprowadziliśmy benchmark, ładując całą zawartość Przygód Sherlocka Holmesa Sir Arthura Conana Doyle’a do String i szukając słowa the w zawartości. Oto wyniki benchmarku dla wersji search używającej pętli for i wersji używającej iteratorów:
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
Obie implementacje mają podobną wydajność! Nie będziemy tutaj wyjaśniać kodu benchmarku, ponieważ nie chodzi o udowodnienie, że obie wersje są równoważne, ale o ogólne rozeznanie, jak te dwie implementacje wypadają pod względem wydajności.
Aby uzyskać bardziej kompleksowy benchmark, powinieneś sprawdzić użycie różnych tekstów o różnych rozmiarach jako contents, różnych słów i słów o różnej długości jako query oraz wszelkiego rodzaju innych wariacji. Chodzi o to: iteratory, choć są abstrakcją wysokiego poziomu, są kompilowane do mniej więcej tego samego kodu, co gdybyś sam napisał kod niższego poziomu. Iteratory to jedna z abstrakcji zerokosztowych Rusta, co oznacza, że użycie abstrakcji nie narzuca żadnego dodatkowego narzutu czasowego. Jest to analogiczne do tego, jak Bjarne Stroustrup, oryginalny projektant i implementator C++, definiuje zerowy narzut w swoim przemówieniu inauguracyjnym ETAPS z 2012 roku „Foundations of C++”:
Ogólnie rzecz biorąc, implementacje C++ przestrzegają zasady zerowego narzutu: Czego nie używasz, za to nie płacisz. A dalej: Czego używasz, nie mógłbyś napisać ręcznie lepiej.
W wielu przypadkach kod Rusta używający iteratorów kompiluje się do tego samego assemblera, co kod napisany ręcznie. Stosowane są optymalizacje, takie jak rozwinięcie pętli i eliminacja sprawdzania zakresów przy dostępie do tablic, co sprawia, że wynikowy kod jest niezwykle wydajny. Teraz, gdy to wiesz, możesz bez obaw używać iteratorów i domknięć! Sprawiają, że kod wydaje się być na wyższym poziomie, ale nie narzucają kary za wydajność w czasie działania.
Podsumowanie
Domknięcia i iteratory to cechy Rusta inspirowane ideami programowania funkcyjnego. Przyczyniają się one do zdolności Rusta do wyraźnego wyrażania idei wysokiego poziomu z niskopoziomową wydajnością. Implementacje domknięć i iteratorów są takie, że wydajność w czasie działania nie jest naruszana. Jest to część celu Rusta, jakim jest dążenie do zapewnienia abstrakcji zerokosztowych.
Teraz, gdy ulepszyliśmy ekspresywność naszego projektu I/O, przyjrzyjmy się kilku innym funkcjom cargo, które pomogą nam udostępnić projekt światu.