2017-07-11 2 views
16

Si je cours ces points de référence à Rust:Pourquoi le logarithme est-il plus lent dans Rust que dans Java?

#[bench] 
fn bench_rnd(b: &mut Bencher) { 
    let mut rng = rand::weak_rng(); 
    b.iter(|| rng.gen_range::<f64>(2.0, 100.0)); 
} 

#[bench] 
fn bench_ln(b: &mut Bencher) { 
    let mut rng = rand::weak_rng(); 
    b.iter(|| rng.gen_range::<f64>(2.0, 100.0).ln()); 
} 

Le résultat est:

test tests::bench_ln    ... bench:  121 ns/iter (+/- 2) 
test tests::bench_rnd   ... bench:   6 ns/iter (+/- 0) 

121-6 = 115 ns par ln appel.

Mais la même référence en Java:

@State(Scope.Benchmark) 
public static class Rnd { 
    final double x = ThreadLocalRandom.current().nextDouble(2, 100); 
} 

@Benchmark 
public double testLog(Rnd rnd) { 
    return Math.log(rnd.x); 
} 

me donne:

Benchmark Mode Cnt Score Error Units 
Main.testLog avgt 20 31,555 ± 0,234 ns/op 

Le journal est ~ 3,7 fois plus lent (115/31) à Rust qu'en Java.

Lorsque je teste l'implémentation de l'hypoténuse (hypot), l'implémentation dans Rust est 15,8 fois plus rapide qu'en Java. Est-ce que j'ai écrit de mauvais repères ou est-ce un problème de performance?

Les réponses aux questions posées dans les commentaires:

  1. "" est un séparateur décimal dans mon pays. Je lance le test de référence de Rust en utilisant cargo bench qui s'exécute toujours en mode release. La structure de référence Java (JMH) crée un nouvel objet pour chaque appel, même s'il s'agit d'une classe static et d'une variable final. Si j'ajoute une création aléatoire dans la méthode testée, j'obtiens 43 ns/op.

+1

Est-ce que java est mauvais à utiliser comme base de référence? Je veux dire que java est sympa mais dans certains cas, c'est trop beau – Wietlol

+3

Vous êtes probablement en train d'évaluer le générateur de nombres aléatoires plus que la fonction log. Aussi, je crois que Rust utilise simplement la bibliothèque mathématique du système, donc un simple appel à 'log' devrait être le même que ce qu'il est en C (aucune idée sur Java). –

+2

Pourriez-vous réexécuter le test en utilisant 'RUSTFLAGS = '- Ctarget-cpu =' banc de chargement 'natif? – kennytm

Répondre

7

La réponse était given by @kennytm:

export RUSTFLAGS='-Ctarget-cpu=native' 

résout le problème. Après cela, les résultats sont les suivants:

test tests::bench_ln    ... bench:   43 ns/iter (+/- 3) 
test tests::bench_rnd    ... bench:   5 ns/iter (+/- 0) 

Je pense que 38 (± 3) est assez proche de 31,555 (± 0,234).

+0

Il est encore plus lent que le code de Java, étonnamment. – Boiethios

6

Je vais fournir l'autre moitié de l'explication puisque je ne connais pas Rust. Math.log est annoté avec @HotSpotIntrinsicCandidate ce qui signifie qu'il sera remplacé par une instruction de processeur natif pour une telle opération: pensez Integer.bitCount qui ferait beaucoup de changements ou utiliserait une instruction de CPU directe qui le fait beaucoup plus rapidement.

Avoir un programme extrêmement simple comme ceci:

public static void main(String[] args) { 
    System.out.println(mathLn(20_000)); 
} 

private static long mathLn(int x) { 
    long result = 0L; 
    for (int i = 0; i < x; ++i) { 
     result = result + ln(i); 
    } 
    return result; 
} 

private static final long ln(int x) { 
    return (long) Math.log(x); 
} 

Et il fonctionne avec:

java -XX:+UnlockDiagnosticVMOptions 
     -XX:+PrintInlining 
     -XX:+PrintIntrinsics 
     -XX:CICompilerCount=2 
     -XX:+PrintCompilation 
     package/Classname 

Il va générer beaucoup de lignes, mais l'un d'entre eux est:

@ 2 java.lang.Math::log (5 bytes) intrinsic 

rendant ce code extrêmement rapide.

Je ne sais pas vraiment quand et comment cela se passe dans Rust si ...

+7

Étant donné que Rust est compilé statiquement (ou AOT, si vous le souhaitez), il doit connaître une seule plate-forme à compiler. Par défaut, ce sera un peu conservateur (le code x86 32 bits peut cibler le processeur 686, par exemple). L'indicateur '-Ctarget-cpu = native' indique au compilateur de cibler la machine sur laquelle s'exécute le compilateur; Cela permet au compilateur d'utiliser l'ensemble complet des instructions disponibles (comme votre exemple 'popcnt'). – Shepmaster