生成随机数的最快方法

8

libc有一个random函数,它使用一个默认大小为31个长整型数的表格,采用非线性加性反馈随机数生成器返回连续的伪随机数。

我正在寻找一个用Rust编写的与其速度相同的随机函数。它不需要是加密安全的,伪随机就足够了。

在查看rand crate后,看起来XorShiftRng是最接近这个需求的:

Xorshift算法不适用于加密目的,但非常快

当我像这样使用它时:

extern crate time;
use rand::Rng;

let mut rng = rand::XorShiftRng::new_unseeded();
rng.next_u64();

这个生成器比libc的随机数慢了约33%。(生成8,000,000个随机数的示例代码)。

最终我需要i64个随机数,因此当我运行rng.gen()时,它已经比libc的随机数慢了100%。当使用rng.next_u64() as i64进行类型转换时,则慢了60%。

在Rust中有没有不使用任何unsafe代码而实现相同速度的方法?


2
只是为了确认一下:你正在使用 Release 模式编译,对吧?(因为当我在你的示例中使用 Release 模式时,XorShiftRng 会被完全优化掉... => http://play.integer32.com/?gist=29f3a3735ee52ab10790f96e4b248b6b&version=nightly) - Matthieu M.
哦,不,我没有!在发布模式下,使用rng.gen()XorShiftRng版本比random()快760,000倍。所以我的问题解决了,但我不明白为什么发布版本会快这么多,以及为什么XorShiftRng现在比random()快这么多。 - hansaplast
5
由于您的基准测试方法有缺陷(很难进行基准测试):所有内容都是硬编码的,所以编译器会优化基准测试为“let start = time(); let end = time(); println!("...", start.to(end));”。这仅适用于XorShiftRng,因为其实现可供编译器使用,而libc::random则是在运行时动态加载的。我的建议是使用真实的工作负载来评估它们。 - Matthieu M.
1个回答

14

请确保在release模式下编译你要测量的代码,否则你的基准测试将不能代表Rust的性能。

为了获得有意义的数字,你还必须修改基准测试以对生成的数字进行操作,例如将它们收集到一个向量中1。如果不这样做,编译器可能会优化整个循环,因为它没有副作用。这就是你第二次尝试发生的事情,导致你得出结论:XorShiftRnglibc::random快760,000倍。

在发布模式下运行更改后的基准测试XorShiftRng的速度约为libc::random的2倍:

PT0.101378490S seconds for libc::random
PT0.050827393S seconds for XorShiftRng

1 编译器也可以聪明到意识到这个向量也没有被使用并且优化掉它,但是目前的 rustc 并没有这样做,并且将元素存储到向量中就足够了。确保生成不被优化掉的一个简单且具有未来性的方法是对数字求和并写出结果。


你的意思是“这样编译器就无法优化它”是什么? - hansaplast
如果编译器检测到您未使用变量,则不会计算其值。 - Peter Hall
@hansaplast Rust的优化编译器将完全省略没有副作用的循环。我已经更新了答案,以更详细地解释这一点。 - user4815162342
最后一个问题:为什么发布版本比调试版本快得多?与没有使用“--release”构建时生成的可执行文件相比,生成的可执行文件有何不同? - hansaplast
2
@hansaplast 构建“发布版”会告诉 rustc(以及底层的 LLVM 后端)进行运行时效率优化。优化包括智能寄存器分配、函数内联、循环展开等等。顺便说一下,C 也是如此,只不过你的发行版已经包含了带有优化的 libc。如果你尝试编译没有打开优化的 libcrandom 函数,你可能会看到极差的性能表现。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接