Skip to content

Commit dd4ad34

Browse files
committed
perf: look up instance __dict__ by the interned name (cached hash) in getattr
generic_getattr_opt looked up the instance dict by name_str.as_wtf8() -- the &Wtf8 content, whose DictKey::key_hash recomputes the SipHash from the raw bytes on every attribute access. Look it up by name_str (the &Py<PyStr>) instead: its DictKey returns the hash PyStr already caches, and adds a pointer-equality fast path for the key compare. (generic_setattr already passed the PyStr, so writes were never affected.) Free -- no memory added; the hash cache already exists. Benchmarks (interleaved A/B vs main, median): attribute-read loop -17.3%, pystone -3.0%, OOP method loop -7.9%; neutral on non-attribute code. Verified: 12 attribute/descriptor/class/dataclass test modules pass; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Assisted-by: Claude:claude-opus-4
1 parent fe2a7db commit dd4ad34

1 file changed

Lines changed: 3 additions & 2 deletions

File tree

crates/vm/src/protocol/object.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,6 @@ impl PyObject {
230230
dict: Option<PyDictRef>,
231231
vm: &VirtualMachine,
232232
) -> PyResult<Option<PyObjectRef>> {
233-
let name = name_str.as_wtf8();
234233
let obj_cls = self.class();
235234
let cls_attr_name = vm.ctx.interned_str(name_str);
236235
let cls_attr = match cls_attr_name.and_then(|name| obj_cls.get_attr(name)) {
@@ -251,7 +250,9 @@ impl PyObject {
251250
let dict = dict.or_else(|| self.dict());
252251

253252
let attr = if let Some(dict) = dict {
254-
dict.get_item_opt(name, vm)?
253+
// Look up by the `PyStr` object (cached hash + pointer-equality fast
254+
// path) rather than its `&Wtf8` content, which re-hashes every call.
255+
dict.get_item_opt(name_str, vm)?
255256
} else {
256257
None
257258
};

0 commit comments

Comments
 (0)