web-dev-qa-db-fra.com

Analyse comparative des programmes dans Rust

Comment est-il possible de comparer des programmes dans Rust? Par exemple, comment puis-je obtenir le temps d'exécution du programme en secondes?

46
php--

Cette réponse est obsolète! Veuillez consulter les réponses ci-dessous pour des informations à jour.


Vous pouvez essayer de chronométrer des composants individuels dans le programme en utilisant caisse de temps .

11
Kevin Cantu

Il pourrait être intéressant de noter 2 ans plus tard (pour aider les futurs programmeurs Rust qui trébuchent sur cette page) qu'il existe maintenant des outils pour comparer Rust code comme une partie de sa suite de tests.

(À partir du lien du guide ci-dessous) En utilisant le #[bench], on peut utiliser l'outillage standard Rust pour comparer les méthodes dans leur code.

extern crate test;
use test::Bencher;

#[bench]
fn bench_xor_1000_ints(b: &mut Bencher) {
    b.iter(|| {
        // use `test::black_box` to prevent compiler optimizations from disregarding
        // unused values
        test::black_box(range(0u, 1000).fold(0, |old, new| old ^ new));
    });
}

Pour la commande cargo bench cela produit quelque chose comme:

running 1 test
test bench_xor_1000_ints ... bench:       375 ns/iter (+/- 148)

test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured

Liens:

83
Michael Tang

Si vous voulez simplement chronométrer un morceau de code, vous pouvez utiliser la caisse time . temps quant à lui déconseillé , cependant. Une caisse de suivi est chrono .

Ajouter time = "*" à ton Cargo.toml.

Ajouter

extern crate time;
use time::PreciseTime;

avant votre fonction principale et

let start = PreciseTime::now();
// whatever you want to do
let end = PreciseTime::now();
println!("{} seconds for whatever you did.", start.to(end));

Exemple complet

Cargo.toml

[package]
name = "hello_world" # the name of the package
version = "0.0.1"    # the current version, obeying semver
authors = [ "[email protected]" ]
[[bin]]
name = "Rust"
path = "Rust.rs"
[dependencies]
Rand = "*" # Or a specific version
time = "*"

Rust.rs

extern crate Rand;
extern crate time;

use Rand::Rng;
use time::PreciseTime;

fn main() {
    // Creates an array of 10000000 random integers in the range 0 - 1000000000
    //let mut array: [i32; 10000000] = [0; 10000000];
    let n = 10000000;
    let mut array = Vec::new();

    // Fill the array
    let mut rng = Rand::thread_rng();
    for _ in 0..n {
        //array[i] = rng.gen::<i32>();
        array.Push(rng.gen::<i32>());
    }

    // Sort
    let start = PreciseTime::now();
    array.sort();
    let end = PreciseTime::now();

    println!("{} seconds for sorting {} integers.", start.to(end), n);
}
41
Martin Thoma

Pour les tests de synchronisation, vous pouvez utiliser std::time::Instant

fn my_function() {
    use std::time::Instant;
    let now = Instant::now();

    {
        my_function_to_measure();
    }

    let elapsed = now.elapsed();
    let sec = (elapsed.as_secs() as f64) + (elapsed.subsec_nanos() as f64 / 1000_000_000.0);
    println!("Seconds: {}", sec);
}

fn main() {
    my_function();
}

Bien sûr, si vous le faisiez souvent, vous voudriez généraliser la conversion, ou utiliser une caisse qui fournit des utilitaires pour cela, ou envelopper Instant et Duration dans vos propres fonctions afin qu'elles puissent être écrites en d'une manière moins verbeuse.

17
ideasman42

Actuellement, il n'y a pas d'interface avec les fonctions Linux suivantes:

  • clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts)
  • getrusage
  • times (page de manuel: man 2 times)

Les moyens disponibles pour mesurer le temps CPU et les hotspots d'un programme Rust sous Linux sont:

  • /usr/bin/time program
  • perf stat program
  • perf record --freq 100000 program; perf report
  • valgrind --tool=callgrind program; kcachegrind callgrind.out.*

La sortie de perf report et valgrind dépend de la disponibilité des informations de débogage dans le programme. Cela peut ne pas fonctionner.

5
user811773

J'ai créé une petite caisse pour cela ( measure_time ), qui enregistre ou imprime le temps jusqu'à la fin de la portée.

#[macro_use]
extern crate measure_time;
fn main() {
    print_time!("measure function");
    do_stuff();
}
2
Pascalius

Il existe plusieurs façons pour évaluer votre programme Rust. Pour la plupart des tests réels, vous devez utiliser un cadre d'analyse comparative approprié, car ils aident avec quelques éléments qui sont facile à visser (y compris l'analyse statistique) Veuillez lire également la section "Pourquoi écrire des repères est difficile" tout en bas!


Rapide et facile: Instant et Duration de la bibliothèque standard

Pour vérifier rapidement la durée d'exécution d'un morceau de code, vous pouvez utiliser les types dans std::time . Le module est assez minimal, mais il convient aux mesures de temps simples. Vous devez utiliser Instant au lieu de SystemTime car la première est une horloge à augmentation monotone et la seconde ne l'est pas. Exemple ( Aire de jeux ):

use std::time::Instant;

let before = Instant::now();
workload();
println!("Elapsed time: {:.2?}", before.elapsed());

La précision de Instant de std n'est malheureusement pas spécifiée dans la documentation, mais sur tous les principaux systèmes d'exploitation, elle utilise la meilleure précision que la plate-forme peut fournir (elle est généralement d'environ 20 ns).

Si std::time n'offre pas suffisamment de fonctionnalités pour votre cas, vous pouvez jeter un œil à chrono . Cependant, pour mesurer les durées, il est peu probable que vous ayez besoin de cette caisse externe.


Utilisation d'un cadre d'analyse comparative

L'utilisation de frameworks est souvent une bonne idée, car ils essaient de vous empêcher de faire des erreurs spécifiques.

Cadre d'analyse comparative intégré de Rust (tous les soirs)

La rouille a une fonction d'analyse comparative intégrée pratique, qui est malheureusement toujours instable en 2019-07. Vous devez ajouter le #[bench] attribuer à votre fonction et lui faire accepter un &mut test::Bencher argument:

#![feature(test)]

extern crate test;
use test::Bencher;

#[bench]
fn bench_workload(b: &mut Bencher) {
    b.iter(|| workload());
}

Exécution de cargo bench affichera:

running 1 test
test bench_workload ... bench:      78,534 ns/iter (+/- 3,606)

test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out

Critère

La caisse criterion est un framework qui fonctionne sur stable, mais il est un peu plus compliqué que la solution intégrée. Il fait une analyse statistique plus sophistiquée, offre une API plus riche, produit plus d'informations et peut même générer automatiquement des graphiques.

Voir la section "Démarrage rapide" pour plus d'informations sur l'utilisation de Criterion.


Pourquoi écrire des repères est difficile

Il existe de nombreux pièges lors de l'écriture de repères. Une seule erreur ne peut que rendre vos résultats de référence dénués de sens. Voici une liste d'erreurs courantes:

  • Compilation avec optimisations : rustc -O3 ou cargo build --release. Lorsque vous exécutez vos benchmarks avec cargo bench, Cargo activera automatiquement les optimisations. Cette étape est importante car il existe souvent une grande différence de performances entre le code optimisé et non optimisé Rust code.
  • Répétez la charge de travail : exécuter une seule fois votre charge de travail est presque toujours inutile. Il y a beaucoup de choses qui peuvent influencer votre timing: la charge globale du système, le système d'exploitation faisant des choses, la limitation du processeur, les caches du système de fichiers, etc. Donc, répétez votre charge de travail aussi souvent que possible. Par exemple, Criterion exécute tous les tests de référence pendant au moins 5 secondes (même si la charge de travail ne prend que quelques nanosecondes). Tous les temps mesurés peuvent ensuite être analysés, la moyenne et l'écart type étant les outils standard.
  • Assurez-vous que votre benchmark n'est pas complètement supprimé : les benchmarks sont par nature très artificiels. Habituellement, le résultat de votre charge de travail n'est pas inspecté car vous souhaitez uniquement mesurer la durée. Cependant, cela signifie qu'un bon optimiseur pourrait supprimer tout votre benchmark car il n'a pas d'effets secondaires (enfin, à part le temps qui passe). Donc, pour tromper l'optimiseur, vous devez en quelque sorte utiliser votre valeur de résultat afin que votre charge de travail ne puisse pas être supprimée. Un moyen simple consiste à imprimer le résultat. Une meilleure solution est quelque chose comme black_box . Cette fonction masque fondamentalement une valeur de LLVM en ce que LLVM ne peut pas savoir ce qui se passera avec la valeur. Rien ne se passe, mais LLVM ne sait pas. C'est le but.

    De bons cadres d'analyse comparative utilisent une boîte à blocs dans plusieurs situations. Par exemple, la fermeture donnée à la méthode iter (pour les deux, intégrée et Criterion Bencher) peut renvoyer une valeur. Cette valeur est automatiquement transmise dans un black_box.

  • Méfiez-vous des valeurs constantes : de manière similaire au point ci-dessus, si vous spécifiez des valeurs constantes dans un benchmark, l'optimiseur peut générer du code spécifiquement pour cette valeur. Dans les cas extrêmes, toute votre charge de travail peut être constamment pliée en une seule constante, ce qui signifie que votre référence est inutile. Passez toutes les valeurs constantes par black_box pour éviter une optimisation trop agressive de LLVM.
  • Attention aux frais généraux de mesure : mesurer une durée prend du temps lui-même. Cela ne représente généralement que des dizaines de nanosecondes, mais peut influencer vos temps mesurés. Donc, pour toutes les charges de travail qui sont plus rapides que quelques dizaines de nanosecondes, vous ne devez pas mesurer chaque temps d'exécution individuellement. Vous pouvez exécuter votre charge de travail 100 fois et mesurer la durée des 100 exécutions. Diviser cela par 100 vous donne le temps unique moyen. Les cadres d'analyse comparative mentionnés ci-dessus utilisent également cette astuce. Criterion a également quelques méthodes pour mesurer des charges de travail très courtes qui ont des effets secondaires (comme la mutation de quelque chose).
  • Beaucoup d'autres choses : malheureusement, je ne peux pas énumérer toutes les difficultés ici. Si vous souhaitez rédiger des références sérieuses, veuillez lire davantage de ressources en ligne.
0
Lukas Kalbertodt