Производительность циклов и итераторов
Чтобы определить, использовать циклы или итераторы, нужно знать, какая
реализация быстрее: версия функции search с явным циклом for или версия с
итераторами.
Мы запустили бенчмарк, загрузив все содержимое The Adventures of Sherlock
Holmes сэра Артура Конан Дойла в String и выполнив поиск слова the в
содержимом. Вот результаты бенчмарка для версии search, использующей цикл
for, и версии, использующей итераторы:
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
У двух реализаций похожая производительность! Мы не будем объяснять код бенчмарка здесь, потому что цель не в том, чтобы доказать эквивалентность двух версий, а в том, чтобы получить общее представление о том, как эти две реализации сравниваются по производительности.
Для более всестороннего бенчмарка стоит проверить разные тексты разного
размера в качестве contents, разные слова и слова разной длины в качестве
query, а также всевозможные другие варианты. Суть вот в чем: итераторы, хотя
и являются высокоуровневой абстракцией, компилируются примерно в тот же код,
как если бы вы сами написали низкоуровневый код. Итераторы – одна из
абстракций с нулевой стоимостью Rust: мы имеем в виду, что использование
абстракции не добавляет накладных расходов во время выполнения. Это похоже на
то, как Бьерн Страуструп, первоначальный проектировщик и реализатор C++,
определяет нулевые накладные расходы в своем докладе “Foundations of C++” на
ETAPS в 2012 году:
В целом реализации C++ следуют принципу нулевых накладных расходов: за то, чем вы не пользуетесь, вы не платите. И далее: то, чем вы пользуетесь, вы не смогли бы написать вручную лучше.
Во многих случаях код Rust, использующий итераторы, компилируется в тот же ассемблерный код, который вы написали бы вручную. Оптимизации вроде разворачивания циклов и устранения проверок границ при доступе к массиву применяются и делают итоговый код чрезвычайно эффективным. Теперь, когда вы это знаете, можете использовать итераторы и замыкания без страха! Они делают код похожим на более высокоуровневый, но не накладывают за это штрафа производительности во время выполнения.
Итоги
Замыкания и итераторы – возможности Rust, вдохновленные идеями языков функционального программирования. Они помогают Rust ясно выражать высокоуровневые идеи с низкоуровневой производительностью. Реализации замыканий и итераторов устроены так, что производительность во время выполнения не страдает. Это часть цели Rust – стремиться предоставлять абстракции с нулевой стоимостью.
Теперь, когда мы улучшили выразительность нашего проекта ввода-вывода,
рассмотрим еще несколько возможностей cargo, которые помогут нам поделиться
проектом с миром.