Skip to content

Commit bbf7aac

Browse files
authored
Merge pull request RustPython#5409 from crazymerlyn/cache-frozenset-hash
Cache hash value for FrozenSets
2 parents d6c1e08 + 48025e0 commit bbf7aac

File tree

2 files changed

+41
-12
lines changed

2 files changed

+41
-12
lines changed

Lib/test/test_set.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -728,15 +728,7 @@ def powerset(s):
728728
for i in range(len(s)+1):
729729
yield from map(frozenset, itertools.combinations(s, i))
730730

731-
# TODO: RUSTPYTHON
732-
# The original test has:
733-
# for n in range(18):
734-
# Due to general performance overhead, hashing a frozenset takes
735-
# about 50 times longer than in CPython. This test amplifies that
736-
# exponentially, so the best we can do here reasonably is 13.
737-
# Even if the internal hash function did nothing, it would still be
738-
# about 40 times slower than CPython.
739-
for n in range(13):
731+
for n in range(18):
740732
t = 2 ** n
741733
mask = t - 1
742734
for nums in (range, zf_range):

vm/src/builtins/set.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ use crate::{
2424
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
2525
};
2626
use once_cell::sync::Lazy;
27+
use rustpython_common::{
28+
atomic::{Ordering, PyAtomic, Radium},
29+
hash,
30+
};
2731
use std::{fmt, ops::Deref};
2832

2933
pub type SetContentType = dictdatatype::Dict<()>;
@@ -71,9 +75,18 @@ impl PySet {
7175
}
7276

7377
#[pyclass(module = false, name = "frozenset", unhashable = true)]
74-
#[derive(Default)]
7578
pub struct PyFrozenSet {
7679
inner: PySetInner,
80+
hash: PyAtomic<PyHash>,
81+
}
82+
83+
impl Default for PyFrozenSet {
84+
fn default() -> Self {
85+
PyFrozenSet {
86+
inner: PySetInner::default(),
87+
hash: hash::SENTINEL.into(),
88+
}
89+
}
7790
}
7891

7992
impl PyFrozenSet {
@@ -87,7 +100,10 @@ impl PyFrozenSet {
87100
inner.add(elem, vm)?;
88101
}
89102
// FIXME: empty set check
90-
Ok(Self { inner })
103+
Ok(Self {
104+
inner,
105+
..Default::default()
106+
})
91107
}
92108

93109
pub fn elements(&self) -> Vec<PyObjectRef> {
@@ -102,6 +118,7 @@ impl PyFrozenSet {
102118
) -> PyResult<Self> {
103119
Ok(Self {
104120
inner: self.inner.fold_op(others, op, vm)?,
121+
..Default::default()
105122
})
106123
}
107124

@@ -115,6 +132,7 @@ impl PyFrozenSet {
115132
inner: self
116133
.inner
117134
.fold_op(std::iter::once(other.into_iterable(vm)?), op, vm)?,
135+
..Default::default()
118136
})
119137
}
120138
}
@@ -472,6 +490,7 @@ impl PySetInner {
472490
op(
473491
&PyFrozenSet {
474492
inner: set.inner.copy(),
493+
..Default::default()
475494
}
476495
.into_pyobject(vm),
477496
vm,
@@ -956,6 +975,7 @@ impl PyFrozenSet {
956975
} else {
957976
Self {
958977
inner: zelf.inner.copy(),
978+
..Default::default()
959979
}
960980
.into_ref(&vm.ctx)
961981
}
@@ -1057,6 +1077,7 @@ impl PyFrozenSet {
10571077
inner: other
10581078
.as_inner()
10591079
.difference(ArgIterable::try_from_object(vm, zelf.into())?, vm)?,
1080+
..Default::default()
10601081
}))
10611082
} else {
10621083
Ok(PyArithmeticValue::NotImplemented)
@@ -1107,7 +1128,23 @@ impl AsSequence for PyFrozenSet {
11071128
impl Hashable for PyFrozenSet {
11081129
#[inline]
11091130
fn hash(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
1110-
zelf.inner.hash(vm)
1131+
let hash = match zelf.hash.load(Ordering::Relaxed) {
1132+
hash::SENTINEL => {
1133+
let hash = zelf.inner.hash(vm)?;
1134+
match Radium::compare_exchange(
1135+
&zelf.hash,
1136+
hash::SENTINEL,
1137+
hash::fix_sentinel(hash),
1138+
Ordering::Relaxed,
1139+
Ordering::Relaxed,
1140+
) {
1141+
Ok(_) => hash,
1142+
Err(prev_stored) => prev_stored,
1143+
}
1144+
}
1145+
hash => hash,
1146+
};
1147+
Ok(hash)
11111148
}
11121149
}
11131150

0 commit comments

Comments
 (0)