为什么 Rust 中的对数比 Java 中的慢?
Why is logarithm slower in Rust than in 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 = 每个 ln
调用 115 ns。
但 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 中的日志比 Java 慢 ~3.7 倍 (115/31)。
当我测试斜边实现时 (hypot
),Rust 中的实现比 Java.
中的实现快 15.8 倍
我写的基准测试不好还是性能问题?
对评论中提出的问题的回复:
","是我国的小数分隔符
我 运行 Rust 的基准测试使用 cargo bench
,在发布模式下总是 运行s。
Java 基准框架 (JMH) 为每次调用创建一个新对象,即使它是 static
class 和 final
多变的。如果我在测试方法中添加一个随机创建,我得到 43 ns/op.
答案是:
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)。
我将提供另一半的解释,因为我不了解 Rust。 Math.log
用 @HotSpotIntrinsicCandidate
注释,这意味着它将被替换为用于此类操作的本机 CPU 指令:想想 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 何时以及如何发生这种情况......
如果我 运行 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 = 每个 ln
调用 115 ns。
但 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 中的日志比 Java 慢 ~3.7 倍 (115/31)。
当我测试斜边实现时 (hypot
),Rust 中的实现比 Java.
我写的基准测试不好还是性能问题?
对评论中提出的问题的回复:
","是我国的小数分隔符
我 运行 Rust 的基准测试使用
cargo bench
,在发布模式下总是 运行s。Java 基准框架 (JMH) 为每次调用创建一个新对象,即使它是
static
class 和final
多变的。如果我在测试方法中添加一个随机创建,我得到 43 ns/op.
答案是
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)。
我将提供另一半的解释,因为我不了解 Rust。 Math.log
用 @HotSpotIntrinsicCandidate
注释,这意味着它将被替换为用于此类操作的本机 CPU 指令:想想 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 何时以及如何发生这种情况......