为什么Python集交集比哈希集交集快?

为什么Python集交集比哈希集交集快?,python,rust,hashset,Python,Rust,Hashset,以下是我的Python代码: len_和=0 对于X范围内的i(100000): set_1=set(X范围(1000)) set_2=set(xrange(5001500)) 交叉点长度=长度(集合1.交叉点(集合2)) len_和+=相交len 打印len_和 这是我的防锈代码: use std::collections::HashSet; fn main() { let mut len_sums = 0; for _ in 0..100000 { let

以下是我的Python代码:

len_和=0
对于X范围内的i(100000):
set_1=set(X范围(1000))
set_2=set(xrange(5001500))
交叉点长度=长度(集合1.交叉点(集合2))
len_和+=相交len
打印len_和
这是我的防锈代码:

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);
}

rustc set\u performance.rs-O
时间./set_性能50000000
真正的0m17.580s
用户0m17.533s
系统0m0.032s
带有
货物的建筑
发布
给出相同的结果

我意识到Python的
set
是用C实现的,因此应该很快,但我没有想到它会比Rust更快。难道它不需要做额外的类型检查而不会生锈吗

也许我在编译我的Rust程序的过程中遗漏了一些东西,我应该使用其他的优化标志吗

另一种可能是,代码不是真正等价的,Rust正在做不必要的额外工作,我遗漏了什么吗

Python版本:

[3]中的
:导入系统
在[4]中:sys.version
Out[4]:“2.7.6(默认,2015年6月22日,17:58:13)\n[GCC 4.8.2]”
铁锈版

$rustc--版本
rustc 1.5.0(3d7cd77e4 2015-12-04)

我使用的是Ubuntu 14.04,我的系统架构是x86_64。

性能问题归结为
HashMap
HashSet
的默认哈希实现。Rust的默认哈希算法是一种很好的通用算法,它还可以防止某些类型的DOS攻击。但是,对于非常小或非常大的数据量,它并不能很好地工作

一些评测显示,
make_hash
占据了总运行时间的41%。截至,您可以选择使用哪个哈希算法。切换到可大大加快程序速度:

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);
}
外部板条箱fnv;
使用std::collections::HashSet;
使用std::hash::BuildHasherDefault;
使用fnv::FnvHasher;
fn main(){
设mut len_和=0;
对于0..100000中的uu{
设set_1:HashSet=(0..1000).collect();
设set_2:HashSet=(500..1500).collect();
让交叉点_len=set_1.交叉点(&set_2).count();
len_sums+=交点len;
}
println!(“{}”,len_和);
}
在我的机器上,与Python的9.203s相比,这需要2.714s


如果你也这么做的话,与Python代码的3.093s相比,Rust代码需要0.829s。

当我将布景建筑移出循环并仅重复交叉点时,当然,对于这两种情况,Rust都比Python 2.7快

我只读过Python3,但是Python的实现有一些东西可以支持它

它使用两个Python集对象使用相同的散列函数这一事实,因此不会重新计算散列。Rust
HashSet
s的散列函数具有实例唯一键,因此在交集期间,它们必须将一个集合中的键与另一个集合的散列函数重新散列

另一方面,Python必须为每个匹配的哈希调用动态键比较函数,如
PyObject\u richcomarebool
,而Rust代码使用泛型,将专门化
i32
的哈希函数和比较代码。Rust中用于散列
i32
的代码看起来相对便宜,并且大部分散列算法(处理长度超过4字节的输入)都被删除了



似乎是集合的构造将Python和Rust区分开来。事实上,不仅仅是构造,还有一些重要的代码正在运行,以破坏Rust
HashSet
s。(这是可以改进的,这里有一个bug:)

撇开散列不谈,当一个小集合和一个大集合以错误的方式相交时,Python会超越以前版本的Rust。例如,这:

使用std::collections::HashSet;
fn main(){
让tiny:HashSet=HashSet::new();
设为:HashSet=(0..1_000).collect();
对于(左、右)in&[(&Twiny,&Twing),(&Twing,&Twiny)]{
让sys_time=std::time::SystemTime::now();
断言(左.交点(右).count(),0);
let appead=sys_time.appead().unwrap();
普林顿(
“{:9}n从{:4}元素集开始”,
已用。subsec_nanos(),
left.len(),
);
}
}
当使用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)
    }
}
fn智能交叉
哪里
T:Eq+std::hash::hash,
S:std::hash::BuildHasher,
{
如果s1.len()
Python中的方法平等地对待这两个集合(至少在版本3.7中是这样)

PS这是为什么? 假设小集Sa有A项,大集Sb有B项,散列一个键需要时间,在包含X个元素的集中找到散列键需要时间Tl(X)。然后:

  • Sa.交叉口(&Sb)
    成本A*(Th+Tl(B))
  • Sb.交叉口(&Sa)
    成本B*(Th+Tl(A))

假设哈希函数是好的,桶是足够的(因为如果我们担心交叉点的性能,那么我们应该确保集合开始时是有效的),然后TL(B)应该与TL(A)相等,或者至少TL(x)应该比设定的大小小得多的线性。因此,决定运营成本的是A对B


PS同样的问题和解决方法存在于
是不相交的
,对于
联合
(复制大集合并添加一些元素比复制小集合并添加很多元素更便宜,但不是很大)。已合并,因此此差异在Rust 1.35之后消失。

当我移动集合时
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)
    }
}