最初我非常惊讶地发现Rust的HashMap
,即使使用FNV
散列器,也比Java,.NET,PHP中的等价物慢得多。我说的是优化的发布模式,我做了一些计算,发现Java/.NET/PHP中的时间低得令人怀疑。然后我突然想到--尽管我是用一个大的哈希表来测试的(数百万个条目),我阅读的大多是连续的 key 值(如14, 15, 16, ...
),这显然导致了 * 大量的 * CPU缓存命中,这是由于这些语言中的标准哈希表(以及整数和短字符串的哈希代码函数)的实现方式,因此具有附近键的条目通常位于附近的内存位置。
另一方面,Rust的HashMap
使用了所谓的SwissTable
实现,它显然以不同的方式分配值。当我测试通过随机键阅读时,一切都很到位-“竞争对手”得分落后于Rust。
因此,如果我们需要按顺序执行大量的get,例如迭代一些有序且大部分顺序的DB ID(没有太多间隙),那么是否有一个好的Rust哈希Map实现可以与Java的HashMap
或.NET的Dictionary
竞争?
P.S.根据评论中的要求,我在这里粘贴了一个示例。我运行了很多测试,但这里有一个简单的示例,在Rust(发布模式)中需要75毫秒,在Java中需要20毫秒:
在Rust中:
let hm: FnvHashMap<i32, i32> = ...;
// Start timer here
let mut sum: i64 = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += *x as i64;
}
}
println!("The sum is: {}", sum);
在Java中:
Map<Integer, Integer> hm = ...;
// Start timer here
long sum = 0;
for (int i = 0; i < 1_000_000; i++) {
sum += hm.get(i);
}
使用HashMap<i32, i32>
和它的默认SipHash
哈希器需要190毫秒。我知道 * 为什么 * 它比FnvHashMap
慢。我只是为了完整性而提到它。
5条答案
按热度按时间p4tfgftt1#
首先,这里有一些可运行的代码来衡量不同实现的效率:
在我写这篇文章的旧桌面机器上,它报告运行76毫秒。由于这台机器已经有10多年的历史了,我发现你的硬件运行同样的代码需要190毫秒,这让我很困惑,所以我想知道你实际上是如何测量的。但是让我们忽略它,集中在相对数字上。
当你关心Rust中hashmap的效率,并且密钥不是来自不可信的来源时,首先要尝试的应该是切换到不抵抗DOS的哈希函数。一种可能性是来自
fnv
机箱的FNV哈希函数,你可以通过将HashMap
切换到fnv::FnvHashMap
来获得。这将性能提高到34毫秒,即2.2倍加速。如果这还不够,您可以尝试使用
rustc-hash
crate中的散列(与fxhash
几乎相同,但allegedly维护得更好),它使用与Rust编译器相同的函数,改编自Firefox使用的哈希。不基于任何形式化分析,它在哈希函数测试套件上表现很差,但据报道,性能始终优于FNV。这在上面的示例中得到了证实,其中从FnvHashMap
切换到rustc_hash::FxHashMap
将时间缩短到28 ms,即从初始时间2.7倍加速。最后,如果你只想模仿C#和Java的操作,并且不太关心某些插入数字的模式会导致性能下降,你可以使用名称恰当的
nohash_hasher
crate,它会给你一个身份散列。将HashMap<i32, i32>
更改为HashMap<i32, i32, nohash_hasher::BuildNoHashHasher<i32>>
将时间降低到不到4毫秒,即从初始时间开始的惊人19倍加速。由于您报告Java示例比Rust快9.5倍,因此19倍的加速应该使您的代码大约是Java的两倍。
bejyjqdl2#
根据用户4815162342的建议,我决定再进行一些测试。这次我使用了另一台安装Ubuntu 20.04的机器。
Rust代码
顺便说一句,最后一个可以替换为这个较短的类型:
对于那些感兴趣的人来说,这是
IntMap
的定义:Java代码
在同一台机器上,我测试了一个Java示例。我没有安装JVM,所以我使用了Docker镜像
adoptopenjdk/openjdk14
,并直接将下面的代码粘贴到jshell>
中(不确定这是否会影响Java的计时)。所以这是Java代码:结果
Rust(释放模式):
Java:
s4chpxco3#
Rust的
HashMap
默认使用SipHash的实现作为哈希函数。SipHash旨在避免基于预测哈希冲突的拒绝服务攻击,这是哈希Map中使用的哈希函数的重要安全属性。如果你不需要这种保证,你可以使用一个更简单的哈希函数。一个选择是使用
fxhash
crate,它应该可以将从HashMap<i32, i32>
阅读整数的速度提高3倍。其他的选择是实现你自己的平凡散列函数(例如,通过简单地使用身份函数,这是一个适合大多数连续键的散列函数),或者使用向量而不是散列Map。
.NET uses the identity function for hashes of
Int32
by default,所以它不能抵抗哈希洪水攻击。当然这更快,但缺点甚至没有在documentation ofDictionary
中提到。无论如何,我更喜欢Rust的“默认安全”方法而不是.NET的任何一天,因为许多开发人员甚至没有意识到可预测的哈希函数可能会导致的问题。如果你不这样做,Rust仍然允许你使用更高性能的哈希函数。不需要哈希泛洪保护,所以对我个人来说,这似乎是Rust的一个优点,至少与.NET相比,而不是弱点。gk7wooem4#
尝试hashbrown
它使用了aHash算法,并与其他HashMap算法here进行了充分的比较
yruzcnhs5#
你也可以试试micromap(我是开发者),对于小Map,它可能比HashMap快5倍以上,因为它根本不使用哈希,也不使用堆。