Производительность: циклы против итераторов
Чтобы решить, что использовать — циклы или итераторы, — нужно знать, какая
реализация быстрее: версия функции search с явным циклом for или версия с
итераторами.
Мы провели замер (benchmark): загрузили в 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 и всякие
другие варианты. Главное вот что: итераторы, хоть и являются
высокоуровневой абстракцией, компилируются примерно в тот же код, какой вы
написали бы сами на низком уровне. Итераторы — одна из абстракций с нулевой
стоимостью (zero-cost abstractions) в Rust: их использование не добавляет
накладных расходов во время выполнения. Это похоже на то, как Бьёрн
Страуструп, создатель и первый разработчик C++, определяет принцип нулевых
накладных расходов в своём докладе 2012 года на ETAPS «Foundations of C++»:
В целом реализации C++ подчиняются принципу нулевых накладных расходов: за то, чем не пользуешься, не платишь. И более того: то, чем пользуешься, вручную лучше не написать.
Во многих случаях код на Rust с итераторами компилируется в тот же ассемблер, что вы написали бы вручную. Применяются оптимизации вроде развёртывания циклов и устранения проверок границ при доступе к массиву, и итоговый код получается чрезвычайно эффективным. Зная это, можно смело пользоваться итераторами и замыканиями! Код с ними выглядит высокоуровневым, но платить за это производительностью во время выполнения не приходится.
Итоги
Замыкания и итераторы — возможности Rust, вдохновлённые идеями функциональных языков программирования. Они помогают Rust ясно выражать высокоуровневые идеи с производительностью низкоуровневого кода. Замыкания и итераторы реализованы так, что на производительность во время выполнения они не влияют. Это часть стремления Rust давать абстракции с нулевой стоимостью.
Теперь, когда наш проект с вводом-выводом стал выразительнее, посмотрим на
другие возможности cargo, которые помогут поделиться проектом с миром.