为什么Rust中的对数比Java慢?

Але*_*ков 16 java floating-point microbenchmark rust jmh

如果我在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());
}
Run Code Online (Sandbox Code Playgroud)

结果是:

test tests::bench_ln             ... bench:        121 ns/iter (+/- 2)
test tests::bench_rnd            ... bench:          6 ns/iter (+/- 0)
Run Code Online (Sandbox Code Playgroud)

每次ln通话121-6 = 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);
}
Run Code Online (Sandbox Code Playgroud)

给我:

Benchmark    Mode Cnt  Score  Error Units
Main.testLog avgt  20 31,555 ± 0,234 ns/op
Run Code Online (Sandbox Code Playgroud)

日志在Rust中比在Java中慢约3.7倍(115/31).

当我测试斜边实现(hypot)时,Rust中的实现比Java快15.8倍.

我写过不好的基准测试,还是性能问题?

回答评论中提出的问题:

  1. ","是我国的小数点分隔符.

  2. 我运行Rust的基准测试cargo bench,总是在发布模式下运行.

  3. Java基准框架(JMH)为每个调用创建一个新对象,即使它是一个static类和一个final变量.如果我在测试方法中添加随机创建,我得到43 ns/op.

Але*_*ков 7

答案由@kennytm给出:

export RUSTFLAGS='-Ctarget-cpu=native'
Run Code Online (Sandbox Code Playgroud)

解决了这个问题.之后,结果是:

test tests::bench_ln              ... bench:          43 ns/iter (+/- 3)
test tests::bench_rnd             ... bench:           5 ns/iter (+/- 0)
Run Code Online (Sandbox Code Playgroud)

我认为38(±3)足够接近31.555(±0.234).


Eug*_*ene 6

因为我不知道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);
}
Run Code Online (Sandbox Code Playgroud)

运行它:

 java -XX:+UnlockDiagnosticVMOptions  
      -XX:+PrintInlining 
      -XX:+PrintIntrinsics 
      -XX:CICompilerCount=2 
      -XX:+PrintCompilation  
      package/Classname 
Run Code Online (Sandbox Code Playgroud)

它会生成很多行,但其中一行是:

 @ 2   java.lang.Math::log (5 bytes)   intrinsic
Run Code Online (Sandbox Code Playgroud)

使这段代码非常快.

我真的不知道什么时候以及如何在Rust中发生......

  • 由于Rust是静态的(或AOT,如果你愿意)编译,它必须知道要编译的单个平台.默认情况下,它将是一种保守的(例如,32位x86代码可能针对686处理器).`-Ctarget-cpu = native`标志告诉编译器以运行编译器的机器为目标; 这允许编译器使用完整的可用指令集(例如你的`popcnt`示例). (8认同)