Merge pull request #5409 from crazymerlyn/cache-frozenset-hash

Cache hash value for FrozenSets
This commit is contained in:
Noa
2024-09-23 17:43:55 -05:00
committed by GitHub
2 changed files with 41 additions and 12 deletions

10
Lib/test/test_set.py vendored
View File

@@ -728,15 +728,7 @@ class TestFrozenSet(TestJointOps, unittest.TestCase):
for i in range(len(s)+1):
yield from map(frozenset, itertools.combinations(s, i))
# TODO: RUSTPYTHON
# The original test has:
# for n in range(18):
# Due to general performance overhead, hashing a frozenset takes
# about 50 times longer than in CPython. This test amplifies that
# exponentially, so the best we can do here reasonably is 13.
# Even if the internal hash function did nothing, it would still be
# about 40 times slower than CPython.
for n in range(13):
for n in range(18):
t = 2 ** n
mask = t - 1
for nums in (range, zf_range):

View File

@@ -24,6 +24,10 @@ use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
};
use once_cell::sync::Lazy;
use rustpython_common::{
atomic::{Ordering, PyAtomic, Radium},
hash,
};
use std::{fmt, ops::Deref};
pub type SetContentType = dictdatatype::Dict<()>;
@@ -71,9 +75,18 @@ impl PySet {
}
#[pyclass(module = false, name = "frozenset", unhashable = true)]
#[derive(Default)]
pub struct PyFrozenSet {
inner: PySetInner,
hash: PyAtomic<PyHash>,
}
impl Default for PyFrozenSet {
fn default() -> Self {
PyFrozenSet {
inner: PySetInner::default(),
hash: hash::SENTINEL.into(),
}
}
}
impl PyFrozenSet {
@@ -87,7 +100,10 @@ impl PyFrozenSet {
inner.add(elem, vm)?;
}
// FIXME: empty set check
Ok(Self { inner })
Ok(Self {
inner,
..Default::default()
})
}
pub fn elements(&self) -> Vec<PyObjectRef> {
@@ -102,6 +118,7 @@ impl PyFrozenSet {
) -> PyResult<Self> {
Ok(Self {
inner: self.inner.fold_op(others, op, vm)?,
..Default::default()
})
}
@@ -115,6 +132,7 @@ impl PyFrozenSet {
inner: self
.inner
.fold_op(std::iter::once(other.into_iterable(vm)?), op, vm)?,
..Default::default()
})
}
}
@@ -472,6 +490,7 @@ impl PySetInner {
op(
&PyFrozenSet {
inner: set.inner.copy(),
..Default::default()
}
.into_pyobject(vm),
vm,
@@ -956,6 +975,7 @@ impl PyFrozenSet {
} else {
Self {
inner: zelf.inner.copy(),
..Default::default()
}
.into_ref(&vm.ctx)
}
@@ -1057,6 +1077,7 @@ impl PyFrozenSet {
inner: other
.as_inner()
.difference(ArgIterable::try_from_object(vm, zelf.into())?, vm)?,
..Default::default()
}))
} else {
Ok(PyArithmeticValue::NotImplemented)
@@ -1107,7 +1128,23 @@ impl AsSequence for PyFrozenSet {
impl Hashable for PyFrozenSet {
#[inline]
fn hash(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
zelf.inner.hash(vm)
let hash = match zelf.hash.load(Ordering::Relaxed) {
hash::SENTINEL => {
let hash = zelf.inner.hash(vm)?;
match Radium::compare_exchange(
&zelf.hash,
hash::SENTINEL,
hash::fix_sentinel(hash),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => hash,
Err(prev_stored) => prev_stored,
}
}
hash => hash,
};
Ok(hash)
}
}