Microbenchmarks

Une autre bonne pratique quand on développe du code sensible aux performances est de mettre en place un système de tests de performances aux résultats relativement reproductibles d’une exécution à l’autre, qui permettent de vérifier rapidement si, quand on modifie du code, les performances changent dans le bon sens ou pas. On parle de microbenchmark.

Conseils d’écriture

Il est important de garder à l’esprit que la reproductibilité et la rapidité de mesure est un compromis que l’on fait au détriment d’autres considérations comme le réalisme. Pour avoir ces propriétés, on doit extraire des petits fragments de code qu’on fait tourner en boucle en faisant des statistiques sur les temps d’exécution. Et les caractéristiques d’un fragment de code qu’on fait tourner en boucle sont souvent un peu différentes de celles du programme complet dont le fragment est extrait.

J’encourage donc les développeurs (et les utilisateurs) à ne pas trop s’intéresser aux nombres absolus qui sortent des microbenchmarks, mais seulement à leur variation relative entre deux implémentations. La performance finale ne peut être mesurée que sur l’application complète, et tout comme un programme développé dans les règles de l’art devrait idéalement avoir à la fois des tests unitaires et des tests d’intégration, vos programmes devraient avoir à la fois des microbenchmarks et des benchmarks complets sur jeux de données réels.

criterion

Il existe une infrastructure de microbenchmark standardisée au sein du compilateur Rust, qui est notamment utilisée pour le développement du compilateur et de la bibliothèque standard. Mais l’équipe de développement de Rust n’est actuellement pas pleinement satisfaite de sa conception, et n’est donc pas encore prête à la stabiliser en l’ouvrant au monde extérieur. Nous ne pouvons donc pas encore l’utiliser sur les versions stables du compilateur Rust.

A la place, je vous incite fortement à utiliser la bibliothèque tierce partie criterion, qui est un portage d’une bibliothèque tierce partie équivalente en Haskell et fait un bon usage des statistiques pour essayer de réduire au maximum le bruit de mesure lié aux autres activités qui se déroulent en tâche de fond sur votre ordinateur pendant que vos microbenchmarks s’exécutent.

L’ergonomie, sans être parfaite, est satisfaisante : en quelques lignes de code…

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use mycrate::fibonacci;

pub fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

…vous pouvez obtenir un jeu de microbenchmarks que vous lancez facilement soit avec cargo bench, l’analogue officiel de cargo test, soit avec l’outil spécialisé cargo criterion qui permet d’obtenir des temps de compilation plus court en dédupliqquant le code commun à tous les benchmarks. Comme avec cargo test, vous pouvez choisir quels benchmarks vous lancez en utilisant une expression régulière, et à la fin vous avez des petits rapports dans votre terminal…

fib 20                  time:   [26.029 us 26.251 us 26.505 us]
Found 11 outliers among 99 measurements (11.11%)
  6 (6.06%) high mild
  5 (5.05%) high severe
slope  [26.029 us 26.505 us] R^2            [0.8745662 0.8728027]
mean   [26.106 us 26.561 us] std. dev.      [808.98 ns 1.4722 us]
median [25.733 us 25.988 us] med. abs. dev. [234.09 ns 544.07 ns]

…qui sont complétés par une version plus complète en HTML pour les experts.

Si par la suite vous modifiez votre code et relancez les benchmarks, vous obtiendrez une analyse statistique de l’évolution de la performance par rapport à l’exécution précédente :

fib 20                  time:   [353.59 ps 356.19 ps 359.07 ps]
                        change: [-99.999% -99.999% -99.999%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 99 measurements (6.06%)
  4 (4.04%) high mild
  2 (2.02%) high severe
slope  [353.59 ps 359.07 ps] R^2            [0.8734356 0.8722124]
mean   [356.57 ps 362.74 ps] std. dev.      [10.672 ps 20.419 ps]
median [351.57 ps 355.85 ps] med. abs. dev. [4.6479 ps 10.059 ps]

Pour plus d’information sur la mise en place, consultez la documentation officielle de criterion.