Тесты производительности
Rust поддерживает тесты производительности, которые помогают измерить
производительность вашего кода. Давайте изменим наш src/lib.rs, чтобы он
выглядел следующим образом (комментарии опущены):
#![feature(test)]
extern crate test;
pub fn add_two(a: i32) -> i32 {
    a + 2
}
#[cfg(test)]
mod tests {
    use super::*;
    use test::Bencher;
    #[test]
    fn it_works() {
        assert_eq!(4, add_two(2));
    }
    #[bench]
    fn bench_add_two(b: &mut Bencher) {
        b.iter(|| add_two(2));
    }
}
Обратите внимание на включение возможности (feature gate) test, что включает
эту нестабильную возможность.
Мы импортировали контейнер test, который включает поддержку измерения
производительности. У нас есть новая функция, аннотированная с помощью атрибута
bench. В отличие от обычных тестов, которые не принимают никаких аргументов,
тесты производительности в качестве аргумента принимают &mut Bencher.
Bencher предоставляет метод iter, который в качестве аргумента принимает
замыкание. Это замыкание содержит код, производительность которого мы хотели бы
протестировать.
Запуск тестов производительности осуществляется командой cargo bench:
$ cargo bench
   Compiling adder v0.0.1 (file:///home/steve/tmp/adder)
     Running target/release/adder-91b3e234d4ed382a
running 2 tests
test tests::it_works ... ignored
test tests::bench_add_two ... bench:         1 ns/iter (+/- 0)
test result: ok. 0 passed; 0 failed; 1 ignored; 1 measured
Все тесты, не относящиеся к тестам производительности, были проигнорированы. Вы,
наверное, заметили, что выполнение cargo bench занимает немного больше времени
чем cargo test. Это происходит потому, что Rust запускает наш тест несколько
раз, а затем выдает среднее значение. Так как мы выполняем слишком мало полезной
работы в этом примере, у нас получается 1 ns/iter (+/- 0), но была бы выведена
дисперсия, если бы был один.
Советы по написанию тестов производительности:
- Внутри 
iterцикла пишите только тот код, производительность которого вы хотите измерить; инициализацию выполняйте за пределамиiterцикла - Внутри 
iterцикла пишите код, который будет идемпотентным (будет делать «то же самое» на каждой итерации); не накапливайте и не изменяйте состояние - Вне 
iterцикла пишите код который также будет идемпотентным; скорее всего, он будет запущен много раз во время теста - Внутри 
iterцикла пишите код, который будет коротким и быстрым, так чтобы запуски тестов происходили быстро и калибратор мог настроить длину пробега с точным разрешением - Внутри 
iterцикла пишите код, делающий что-то простое, чтобы помочь в выявлении улучшения (или уменьшения) производительности 
Особенности оптимизации
А вот другой сложный момент, относящийся к написанию тестов производительности: тесты, скомпилированные с оптимизацией, могут быть значительно изменены оптимизатором, после чего тест будет мерить производительность не так, как мы этого ожидаем. Например, компилятор может определить, что некоторые выражения не оказывают каких-либо внешних эффектов и просто удалит их полностью.
#![feature(test)]
extern crate test;
use test::Bencher;
#[bench]
fn bench_xor_1000_ints(b: &mut Bencher) {
    b.iter(|| {
        (0..1000).fold(0, |old, new| old ^ new);
    });
}
выведет следующие результаты
running 1 test
test bench_xor_1000_ints ... bench:         0 ns/iter (+/- 0)
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured
Движок для запуска тестов производительности оставляет две возможности,
позволяющие этого избежать. Либо использовать замыкание, передаваемое в метод
iter, которое возвращает какое-либо значение; тогда это заставит оптимизатор
думать, что возвращаемое значение будет использовано, из-за чего удалить
вычисления полностью будет не возможно. Для примера выше этого можно достигнуть,
изменив вызова b.iter
# struct X;
# impl X { fn iter<T, F>(&self, _: F) where F: FnMut() -> T {} } let b = X;
b.iter(|| {
    // note lack of `;` (could also use an explicit `return`).
    (0..1000).fold(0, |old, new| old ^ new)
});
Либо использовать вызов функции test::black_box, которая представляет собой
«черный ящик», непрозрачный для оптимизатора, тем самым заставляя его
рассматривать любой аргумент как используемый.
#![feature(test)]
extern crate test;
# fn main() {
# struct X;
# impl X { fn iter<T, F>(&self, _: F) where F: FnMut() -> T {} } let b = X;
b.iter(|| {
    let n = test::black_box(1000);
    (0..n).fold(0, |a, b| a ^ b)
})
# }
В этом примере не происходит ни чтения, ни изменения значения, что очень дешево
для малых значений. Большие значения могут быть переданы косвенно для уменьшения
издержек (например, black_box(&huge_struct)).
Выполнение одного из вышеперечисленных изменений дает следующие результаты измерения производительности
running 1 test
test bench_xor_1000_ints ... bench:       131 ns/iter (+/- 3)
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured
Тем не менее, оптимизатор все еще может вносить нежелательные изменения в определенных случаях, даже при использовании любого из вышеописанных приемов.