Aka*_*all 23 python hashset rust
这是我的Python代码:
len_sums = 0
for i in xrange(100000):
set_1 = set(xrange(1000))
set_2 = set(xrange(500, 1500))
intersection_len = len(set_1.intersection(set_2))
len_sums += intersection_len
print len_sums
Run Code Online (Sandbox Code Playgroud)
这是我的Rust代码:
use std::collections::HashSet;
fn main() {
let mut len_sums = 0;
for _ in 0..100000 {
let set_1: HashSet<i32> = (0..1000).collect();
let set_2: HashSet<i32> = (500..1500).collect();
let intersection_len = set_1.intersection(&set_2).count();
len_sums += intersection_len;
}
println!("{}", len_sums);
}
Run Code Online (Sandbox Code Playgroud)
我相信这些大致相当.我得到以下表现结果:
time python set_performance.py
50000000
real 0m11.757s
user 0m11.736s
sys 0m0.012s
Run Code Online (Sandbox Code Playgroud)
和
rustc set_performance.rs -O
time ./set_performance 50000000
real 0m17.580s
user 0m17.533s
sys 0m0.032s
Run Code Online (Sandbox Code Playgroud)
(建立cargo
并--release
给出相同的结果).
我意识到Python set
是用C实现的,所以预计会很快,但我没想到它会比Rust更快.难道不必做Rust不会做的额外类型检查吗?
也许我在编译Rust程序的过程中遗漏了一些东西,我应该使用其他任何优化标志吗?
另一种可能性是代码并不是真正等效的,Rust正在做不必要的额外工作,我错过了什么吗?
Python版本:
In [3]: import sys
In [4]: sys.version
Out[4]: '2.7.6 (default, Jun 22 2015, 17:58:13) \n[GCC 4.8.2]'
Run Code Online (Sandbox Code Playgroud)
Rustc版本(我知道1.6已经发布)
$ rustc --version
rustc 1.5.0 (3d7cd77e4 2015-12-04)
Run Code Online (Sandbox Code Playgroud)
我正在使用cargo
,我的系统架构是x86_64.
blu*_*uss 20
当我将集合构建移出循环并且仅重复交集时,对于这两种情况,Rust当然比Python 2.7更快.
我只是在阅读Python 3 (setobject.c),但Python的实现有一些事情要做.
它使用两个Python集合对象使用相同的散列函数的事实,因此它不会重新计算散列.Rust HashSet
的哈希函数具有实例唯一键,因此在交集期间,它们必须使用另一个集合的哈希函数从一个集合中重新发送键.
另一方面,Python必须调用动态密钥比较函数,例如PyObject_RichCompareBool
每个匹配的哈希,而Rust代码使用泛型,并将专门化哈希函数和比较代码i32
.i32
在Rust中散列an的代码看起来相对便宜,并且删除了大部分散列算法(处理比4个字节更长的输入).
它似乎是将 Python和Rust分开的集合的构造.事实上,不仅仅是构造,还有一些重要的代码可以用来破坏Rust HashSet
.(这可以改进,提出错误:#31711)
She*_*ter 17
性能问题归结为默认的散列实现的HashMap
和HashSet
.Rust的默认哈希算法是一个很好的通用目标,它也可以防止某些类型的DOS攻击.但是,它对于非常小或非常大量的数据不起作用.
一些分析显示,make_hash<i32, std::collections::hash::map::RandomState>
占总运行时间的约41%.从Rust 1.7开始,您可以选择使用哪种哈希算法.切换到FNV哈希算法会大大加快程序的速度:
extern crate fnv;
use std::collections::HashSet;
use std::hash::BuildHasherDefault;
use fnv::FnvHasher;
fn main() {
let mut len_sums = 0;
for _ in 0..100000 {
let set_1: HashSet<i32, BuildHasherDefault<FnvHasher>> = (0..1000).collect();
let set_2: HashSet<i32, BuildHasherDefault<FnvHasher>> = (500..1500).collect();
let intersection_len = set_1.intersection(&set_2).count();
len_sums += intersection_len;
}
println!("{}", len_sums);
}
Run Code Online (Sandbox Code Playgroud)
在我的机器上,与Python的9.203相比,这需要2.714秒.
如果进行相同的更改以将集合构建移出循环,则与Python代码的3.093相比,Rust代码需要0.829秒.
抛开这些,当您以错误的方式相交一个微小的对象时,Python会超越Rust的早期版本。例如在操场上的这段代码:
use std::collections::HashSet;
fn main() {
let tiny: HashSet<i32> = HashSet::new();
let huge: HashSet<i32> = (0..1_000).collect();
for (left, right) in &[(&tiny, &huge), (&huge, &tiny)] {
let sys_time = std::time::SystemTime::now();
assert_eq!(left.intersection(right).count(), 0);
let elapsed = sys_time.elapsed().unwrap();
println!(
"{:9}ns starting from {:4} element set",
elapsed.subsec_nanos(),
left.len(),
);
}
}
Run Code Online (Sandbox Code Playgroud)
当使用1.32或更早版本的Rust(而不是当前版本)运行时,这表明您确实要在两个集合中的较小者上调用交集方法(即使在临界情况下,一个集合为空)。通过调用此函数而不是交集方法,我获得了不错的性能提升:
fn smart_intersect<'a, T, S>(
s1: &'a HashSet<T, S>,
s2: &'a HashSet<T, S>,
) -> std::collections::hash_set::Intersection<'a, T, S>
where
T: Eq + std::hash::Hash,
S: std::hash::BuildHasher,
{
if s1.len() < s2.len() {
s1.intersection(s2)
} else {
s2.intersection(s1)
}
}
Run Code Online (Sandbox Code Playgroud)
Python中的方法平等地对待两个集合(至少在3.7版中)。
PS这是为什么?假设小集合Sa具有A个项目,大集合Sb具有B个项目,则花Th时间来哈希一个键,Tl(X)时间来定位具有X个元素的集合中的哈希键。然后:
Sa.intersection(&Sb)
成本A *(Th + Tl(B))Sb.intersection(&Sa)
成本B *(Th + Tl(A))假设哈希函数很好并且存储桶足够多(因为如果我们担心交集的性能,那么我们应该确保集合有效地开始),那么Tl(B)应该与Tl(A ),或者至少Tl(X)的缩放比例应远小于设置大小的线性比例。因此,决定成本的是A与B。
PS存在相同的问题和解决方法is_disjoint
,也有一些问题union
(复制大集合并添加一些元素要便宜,比复制小集合并添加很多元素要便宜,但幅度不大)。由于合并了拉取请求,因此从Rust 1.35开始,这种差异消失了。
归档时间: |
|
查看次数: |
3188 次 |
最近记录: |