Производительность циклов против итераторов
Чтобы определить, использовать ли циклы или итераторы, вам нужно знать, какая реализация быстрее: версия функции search с явным циклом for или версия с итераторами.
Мы провели бенчмарк, загрузив полное содержание «Приключений Шерлока Холмса» сэра Артура Конан Дойла в 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 «Основы C++»:
В общем случае реализации C++ подчиняются принципу нулевой стоимости: за то, что вы не используете, вы не платите. И далее: то, что вы используете, вы не смогли бы написать вручную лучше.
Во многих случаях Rust-код, использующий итераторы, компилируется в тот же ассемблерный код, который вы написали бы вручную. Оптимизации, такие как развертывание циклов (loop unrolling) и устранение проверок границ при доступе к массиву, применяются и делают результирующий код чрезвычайно эффективным. Теперь, когда вы это знаете, вы можете использовать итераторы и замыкания без страха! Они делают код более высокоуровневым, но не накладывают штрафа за производительность во время выполнения за это.
Итоги
Замыкания и итераторы — это возможности Rust, вдохновленные идеями функционального программирования. Они способствуют возможности Rust четко выражать высокоуровневые идеи при низкоуровневой производительности. Реализации замыканий и итераторов таковы, что производительность во время выполнения не страдает. Это часть цели Rust — стремиться предоставлять абстракции с нулевой стоимостью.
Теперь, когда мы улучшили выразительность нашего I/O проекта, давайте рассмотрим некоторые другие возможности cargo, которые помогут нам поделиться проектом с миром.