< 返回版块

hzqd 发表于 2024-06-27 01:33

我测试了“并行估算圆周率”的 Rust 和 Java 代码:

use rand::{thread_rng, Rng};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
const N: i32 = i32::MAX;
fn main() {
    let n: i32 = (0..N).into_par_iter().map(|_| {
        let mut rng = thread_rng();
        let x = rng.gen_range(0.0..1.0) - 0.5f64;
        let y = rng.gen_range(0.0..1.0) - 0.5f64;
        if x.powf(2.0) + y.powf(2.0) < 0.25 { 1 } else { 0 }
    }).sum();
    let pi = n as f64 / N as f64 * 4.0;
    println!("π ≈ {pi}");
}
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.IntStream;
public class Main {
    static final int N = Integer.MAX_VALUE;
    public static void main(String[] args) {
        int count = IntStream.range(0, N).parallel().map(i -> {
            double x = ThreadLocalRandom.current().nextDouble(0.0, 1.0) - 0.5;
            double y = ThreadLocalRandom.current().nextDouble(0.0, 1.0) - 0.5;
            return (x * x + y * y < 0.25) ? 1 : 0;
        }).sum();
        double pi = (double) count / N * 4.0;
        System.out.println("π ≈ " + pi);
    }
}

Rust版本在我的电脑上执行耗时7秒,而Java耗时仅3秒。 有没有大佬可以解释这是为什么呀?

评论区

写评论
FyLost 2024-07-05 21:32

Java 版本在我的电脑上耗时 1.7 秒,Rust 版本耗时 3 秒,但是下面这样写的话则只需要 0.58 秒:

use rand::prelude::*;
use rayon::prelude::*;
const N: i32 = i32::MAX;
fn main() {
    let n = (0..N)
        .into_par_iter()
        .map_init(|| SmallRng::from_rng(thread_rng()).unwrap(), |rng, _| {
            let a: f64 = rng.gen_range(-0.5..0.5);
            let b: f64 = rng.gen_range(-0.5..0.5);
            if a * a + b * b <= 0.25 {
                1
            } else {
                0
            }
        })
        .sum::<i32>();
    let pi = n as f64 / N as f64 * 4.0;
    println!("π ≈ {pi}");
}

其中关键点有两个,一个是因为不需要加密所以换用更快的 SmallRngthread_rng() 默认使用加密算法),另一个是改用 map_init 来避免大量的随机数生成器初始化所带来的开销。

Bai-Jinlin 2024-06-28 12:32

希望每个怀疑性能问题的都起码学一下性能刨析软件如何使用。

例如你这个问题,在Linux下cargo build --release编译后运行perf record -F 99 -a -- ./target/release/<你的程序名>,然后执行perf report,你就可以看到最耗时的是rand::rng::Rng::gen_range以及rand_chacha::guts::refill_wide::impl_avx2,进而猜测出问题出在随机数生成的库上。

aj3n 2024-06-27 18:30

和迭代器没关系,就是rng的性能差异;

多线程的程序不好跨机器比较性能,看其他人的回复有不少2-3s的运行时间,我的16c32t机器上运行时间只有0.8s,把原程序改成单线程版本(数据量缩小1/8), cpu用5700u;

use rand::{thread_rng, Rng};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
const N: i32 = i32::MAX;
fn main() {
    let mut rng = thread_rng();
    let n: i32 = (0..N / 8)
        .map(|_| {
            let x = rng.gen_range(0.0..1.0) - 0.5f64;
            let y = rng.gen_range(0.0..1.0) - 0.5f64;
            if x * x + y * y < 0.25 {
                1
            } else {
                0
            }
        })
        .sum();
    let pi = n as f64 / N as f64 * 4.0;
    println!("π ≈ {pi}");
}

运行时间:

________________________________________________________
Executed in    2.67 secs    fish           external
   usr time    2.61 secs  304.00 micros    2.61 secs
   sys time    0.05 secs  160.00 micros    0.05 secs
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.IntStream;
public class Main {
    static final int N = Integer.MAX_VALUE;
    public static void main(String[] args) {
        int count = IntStream.range(0, N / 8).map(i -> {
            double x = ThreadLocalRandom.current().nextDouble(0.0, 1.0) - 0.5;
            double y = ThreadLocalRandom.current().nextDouble(0.0, 1.0) - 0.5;
            return (x * x + y * y < 0.25) ? 1 : 0;
        }).sum();
        double pi = (double) count / N * 4.0;
        System.out.println("π ≈ " + pi);
    }
}

运行时间(openjdk 22 2024-03-19):

________________________________________________________
Executed in    1.76 secs    fish           external
   usr time    1.75 secs  388.00 micros    1.75 secs
   sys time    0.03 secs  226.00 micros    0.03 secs

rust版本改用SmallRng(feature small_rng):

use rand::{SeedableRng, rngs::SmallRng, Rng};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
const N: i32 = i32::MAX;
fn main() {
    let mut rng = SmallRng::from_entropy();
    let n: i32 = (0..N / 8)
        .map(|_| {
            let x = rng.gen_range(0.0..1.0) - 0.5f64;
            let y = rng.gen_range(0.0..1.0) - 0.5f64;
            if x * x + y * y < 0.25 {
                1
            } else {
                0
            }
        })
        .sum();
    let pi = n as f64 / N as f64 * 4.0;
    println!("π ≈ {pi}");
}

运行时间

Executed in  764.32 millis    fish           external
   usr time  739.31 millis  405.00 micros  738.91 millis
   sys time   23.52 millis  260.00 micros   23.27 millis
hamflx 2024-06-27 11:17

看文档 randthread_rng 是 cryptographically secure,但是 javaThreadLocalRandom 不是,所以 rust 的慢很多。

hamflx 2024-06-27 10:38

换成 fastrand 后,就快了好多:

use rayon::iter::{IntoParallelIterator, ParallelIterator};
const N: i32 = i32::MAX;
fn main() {
    let n: i32 = (0..N)
        .into_par_iter()
        .map(|_| {
            let x = fastrand::f64() - 0.5f64;
            let y = fastrand::f64() - 0.5f64;
            if x.powf(2.0) + y.powf(2.0) < 0.25 {
                1
            } else {
                0
            }
        })
        .sum();
    let pi = n as f64 / N as f64 * 4.0;
    println!("π ≈ {pi}");
}
D:\playground\pi> timeit {.\target\release\pi.exe}
π ≈ 3.1415898404743476
3sec 467ms 863µs 400ns
hamflx 2024-06-27 10:25

有可能是生成随机数慢:

use rand::{thread_rng, Rng};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
const N: i32 = i32::MAX;
fn main() {
    for i in 0..N {
        let mut rng = thread_rng();
        rng.gen_range(0.0..1.0);
    }
}
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.IntStream;
public class Main {
    static final int N = Integer.MAX_VALUE;
    public static void main(String[] args) {
        for (int i = 0; i < N; i++) {
            ThreadLocalRandom.current().nextDouble(0.0, 1.0);
        }
    }
}
D:\playground\pi> timeit {.\target\release\pi.exe}
16sec 357ms 43µs

D:\playground\pi-java> timeit {java Main}
7sec 793ms 44µs 500ns
hamflx 2024-06-27 09:57

刚刚看到大佬在知乎上评仓颉,不知道仓颉的版本要多长时间 🤣

  • debug 等了一会,等不出结果,放弃了
  • release 6s 7s 的样子
  • java 4.5s
lsk569937453 2024-06-27 09:22

.\target\release\siu.exe π ≈ 3.141593321758133 2354ms

2.3秒的路过

JackySu 2024-06-27 08:39

没开优化?我rust跑2.8秒

1 共 9 条评论, 1 页