2017-07-11 67 views
16

Gdybym uruchomić te standardy w Rust:Dlaczego logarytm jest wolniejszy w Rust niż w Javie?

#[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()); 
} 

Wynikiem jest:

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

121-6 = 115 ns na ln rozmowy.

Ale ten sam punkt odniesienia w Javie:

@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); 
} 

Daje mi:

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

Rejestr jest ~ 3,7 razy wolniej (115/31) w Rust niż w Javie.

Kiedy testuję wykonanie przeciwprostokątnej (hypot), implementacja w Rust jest 15,8 razy szybsza niż w Javie.

Czy napisałem złe testy porównawcze lub jest to problem z wydajnością?

Odpowiedzi na pytania zadane w komentarzach:

  1. "" jest separator dziesiętny w moim kraju.

  2. Prowadzę benchmark Rusta przy użyciu cargo bench, który zawsze działa w trybie zwolnienia.

  3. Środowisko testowe Java (JMH) tworzy nowy obiekt dla każdego połączenia, mimo że jest to klasa static i final zmienna. Jeśli dodaję losową kreację do testowanej metody, otrzymam 43 ns/op.

+1

Czy nie można używać java jako punktu odniesienia? Chodzi mi o to, że Java jest fajna, ale w niektórych przypadkach jest zbyt fajna. – Wietlol

+3

Prawdopodobnie porównujesz generator liczb losowych bardziej niż funkcję rejestru. Wierzę również, że Rust po prostu używa systemowej biblioteki matematycznej, więc czyste wywołanie 'log' powinno być takie samo jak w C (nie ma pojęcia o Javie). –

+2

Czy możesz ponownie uruchomić test, używając 'RUSTFLAGS = '- Ctarget-cpu = rodzimej" ławki ładunkowej "? – kennytm

Odpowiedz

7

Odpowiedź była given by @kennytm:

export RUSTFLAGS='-Ctarget-cpu=native' 

rozwiązuje problem. Następnie wyniki:

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

Myślę, że 38 (± 3) jest wystarczająco blisko 31,555 (± 0,234).

+0

Zaskakująco jest to wolniejsze niż kod Java. – Boiethios

6

Mam zamiar podać drugą połowę wyjaśnienia, ponieważ nie znam Rusta. Math.log jest opatrzone komentarzem o numerze @HotSpotIntrinsicCandidate, co oznacza, że ​​zostanie zastąpione przez natywną instrukcję procesora dla takiej operacji: pomyśl o Integer.bitCount, które albo wykonałoby wiele zmian, albo użyje instrukcji bezpośredniego procesora, która robi to znacznie szybciej.

uwzględniając niezwykle prosty program tak:

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); 
} 

i uruchomienie go z:

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

To będzie generować wiele wierszy, ale jeden z nich jest:

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

ten kod jest niezwykle szybki.

Naprawdę nie wiem, kiedy i jak to się dzieje w Rust ...

+7

Ponieważ Rust jest statycznie (lub AOT, jeśli chcesz) skompilowany, musi znać jedną platformę do kompilacji. Domyślnie będzie to trochę konserwatywne (na przykład 32-bitowy kod x86 może być kierowany na procesor 686). Flaga '-Ctarget-cpu = native' mówi kompilatorowi, aby celował w maszynę, na której działa kompilator; to pozwala kompilatorowi korzystać z pełnego zestawu dostępnych instrukcji (np. twojego przykładu 'popcnt'). – Shepmaster

Powiązane problemy