我在java和rust上运行了一个完全相同的小基准测试。
java 语:
public class Main {
private static final int NUM_ITERS = 100;
public static void main(String[] args) {
long tInit = System.nanoTime();
int c = 0;
for (int i = 0; i < NUM_ITERS; ++i) {
for (int j = 0; j < NUM_ITERS; ++j) {
for (int k = 0; k < NUM_ITERS; ++k) {
if (i*i + j*j == k*k) {
++c;
System.out.println(i + " " + j + " " + k);
}
}
}
}
System.out.println(c);
System.out.println(System.nanoTime() - tInit);
}
}
生 rust :
use std::time::SystemTime;
const NUM_ITERS: i32 = 100;
fn main() {
let t_init = SystemTime::now();
let mut c = 0;
for i in 0..NUM_ITERS {
for j in 0..NUM_ITERS {
for k in 0..NUM_ITERS {
if i*i + j*j == k*k {
c += 1;
println!("{} {} {}", i, j, k);
}
}
}
}
println!("{}", c);
println!("{}", t_init.elapsed().unwrap().as_nanos());
}
什么时候 NUM_ITERS = 100
,正如预期的那样,rust-out执行了java
Java: 59311348 ns
Rust: 29629242 ns
但对于 NUM_ITERS = 1000
,我发现生 rust 的时间要长得多,而java的速度要快得多
Java: 1585835361 ns
Rust: 28623818145 ns
为什么会这样?在这种情况下,rust的性能不应该比java更好吗?还是因为我在执行过程中犯了一些错误?
更新
我去掉了线 System.out.println(i + " " + j + " " + k);
以及 println!("{} {} {}", i, j, k);
从密码里。这是输出
NUM_ITERS = 100
Java: 3843114 ns
Rust: 29072345 ns
NUM_ITERS = 1000
Java: 1014829974 ns
Rust: 28402166953 ns
所以,如果没有 println
在这两种情况下,java的性能都比rust好。我只是想知道为什么会这样。java运行垃圾收集器和其他开销。我没有在rust中实现最佳的循环吗?
1条答案
按热度按时间l7wslrjt1#
我调整了你的代码以消除评论中提出的批评。不为生产编译rust是最大的问题,这会带来50倍的开销。除此之外,我在测量时消除了打印,并对java代码进行了适当的预热。
我要说的是,在这些更改之后,java和rust并驾齐驱,它们彼此相差不到2倍,而且每次迭代的成本都非常低(只有几分之一纳秒)。
这是我的密码:
我使用jdk16从intellij运行java。我从命令行运行rust,使用
cargo run --release
.java输出示例:
生 rust 输出示例:
当我看到java给出更好的结果(它的jit编译器已经优化了20年了,没有对象分配,所以没有gc)时,我并不感到惊讶,但是我对迭代的总体低成本感到困惑。我们可以假设
i*i + j*j
被吊出内环k*k
在里面。我用反汇编器检查产生的代码。它肯定涉及最里面的循环imul。我读了这个答案,它说英特尔有一个imul指令只有3个cpu周期的延迟。再加上多个alu和指令并行性,每次迭代1个周期的结果就更可信了。
我发现的另一件有趣的事是,如果我检查一下
c % 137 == 0
但不要打印c
在铁 rust 里println!
语句,(仅打印“count可被137整除”),迭代成本降到仅0.26ns。所以,当我没有要求精确的值时,rust能够从循环中消除很多工作c
.更新
正如在@trentci的注解中所讨论的,我更完整地模拟了java代码,添加了一个重复测量的外部循环,现在它位于一个单独的函数中:
现在我得到这个输出:
代码的另一个细微变化导致了性能的显著变化。不过,它也显示了rust优于java的一个关键优势:无需预热即可获得最佳性能。