Почему логарифм медленнее в Rust, чем в Java?
Если я запустил эти тесты в Rust:
#[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());
}
Результат:
test tests::bench_ln ... bench: 121 ns/iter (+/- 2)
test tests::bench_rnd ... bench: 6 ns/iter (+/- 0)
121-6 = 115 нс на вызов ln
.
Но тот же тест в 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);
}
Дает мне:
Benchmark Mode Cnt Score Error Units
Main.testLog avgt 20 31,555 ± 0,234 ns/op
В Rust log более ~ 3.7 раз медленнее (115/31), чем в Java.
Когда я тестирую реализацию гипотенузы (hypot
), реализация в Rust в 15,8 раза быстрее, чем в Java.
Я написал плохие тесты или это проблема производительности?
Ответы на вопросы, заданные в комментариях:
-
"," является десятичным разделителем в моей стране.
-
Я запускаю тест Rust с помощью cargo bench
, который всегда запускается в режиме выпуска.
-
Базовая среда Java (JMH) создает новый объект для каждого вызова, хотя это класс static
и переменная final
. Если я добавлю случайное создание в тестируемый метод, я получаю 43 нс/оп.
Ответы
Ответ 1
Ответ был указанный @kennytm:
export RUSTFLAGS='-Ctarget-cpu=native'
Устраняет проблему. После этого результаты:
test tests::bench_ln ... bench: 43 ns/iter (+/- 3)
test tests::bench_rnd ... bench: 5 ns/iter (+/- 0)
Я думаю, что 38 (± 3) достаточно близко к 31.555 (± 0.234).
Ответ 2
Я собираюсь предоставить вторую половину объяснения, так как я не знаю Ржавчина. Math.log
аннотируется с помощью @HotSpotIntrinsicCandidate
, что означает, что он будет заменен нативной инструкцией процессора для такой операции: подумайте Integer.bitCount
, которая будет либо много менять, либо использовать прямую инструкцию CPU, которая делает это намного быстрее.
Имея чрезвычайно простую программу:
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);
}
И запустите его с помощью
java -XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining
-XX:+PrintIntrinsics
-XX:CICompilerCount=2
-XX:+PrintCompilation
package/Classname
Он будет генерировать много строк, но один из них:
@ 2 java.lang.Math::log (5 bytes) intrinsic
делает этот код очень быстрым.
Я не знаю, когда и как это происходит в Rust, хотя...