Skip to content

gh-146558: optimize dict access with objects with known hash - #146559

Merged
kumaraditya303 merged 8 commits into
python:mainfrom
kumaraditya303:jit-hash
Mar 30, 2026
Merged

gh-146558: optimize dict access with objects with known hash#146559
kumaraditya303 merged 8 commits into
python:mainfrom
kumaraditya303:jit-hash

Conversation

@kumaraditya303

@kumaraditya303kumaraditya303 commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

I asked Claude to create a benchmark for this, it created the following script which shows a real significant speedup.

BenchmarkNo JITJITSpeedup
dict_get[str]55.7M/s70.1M/s+26%
dict_get[int]55.5M/s75.4M/s+36%
dict_get[float]50.9M/s74.7M/s+47%
dict_get[complex]44.9M/s74.9M/s+67%
dict_store[str]74.9M/s85.7M/s+14%
dict_store[int]76.0M/s94.1M/s+24%
dict_store[float]66.6M/s97.0M/s+46%
dict_store[complex]55.9M/s98.0M/s+75%
dict_get[multi]23.4M/s35.1M/s+50%
dict_get[obj]52.9M/s68.8M/s+30%
dict_store[obj]75.4M/s92.5M/s+23%
Details
"""Benchmark dict get/store with constant keys to measure_BINARY_OP_SUBSCR_DICT_KNOWN_HASH and _STORE_SUBSCR_DICT_KNOWN_HASH speedup.Usage: PYTHON_JIT=0 ./python.exe bench_dict_known_hash.py PYTHON_JIT=1 ./python.exe bench_dict_known_hash.py"""importtimeimportosimportsysN=50_000_000defbench_get_str(n):
d= {'a': 1, 'b': 2, 'c': 3}
x=0for_inrange(n):
x+=d['a']
returnxdefbench_get_int(n):
d= {1: 10, 2: 20, 3: 30}
x=0for_inrange(n):
x+=d[1]
returnxdefbench_store_str(n):
d= {}
for_inrange(n):
d['a'] =1returnddefbench_store_int(n):
d= {}
for_inrange(n):
d[1] =1returnddefbench_get_float(n):
d= {1.5: 1, 2.5: 2, 3.5: 3}
x=0for_inrange(n):
x+=d[1.5]
returnxdefbench_store_float(n):
d= {}
for_inrange(n):
d[1.5] =1returnddefbench_get_complex(n):
d= {1+2j: 1, 3+4j: 2}
x=0for_inrange(n):
x+=d[1+2j]
returnxdefbench_store_complex(n):
d= {}
for_inrange(n):
d[1+2j] =1returnddefbench_get_multi(n):
d= {'a': 1, 1: 2, b'x': 3, (1, 2): 4}
x=0for_inrange(n):
x+=d['a'] +d[1] +d[b'x'] +d[(1, 2)]
returnxclass_Key:
pass_KEY=_Key()
defbench_get_obj(n):
d= {_KEY: 1}
x=0for_inrange(n):
x+=d[_KEY]
returnxdefbench_store_obj(n):
d= {}
for_inrange(n):
d[_KEY] =1returnddefrun_avg(name, func, n, runs=3):
func(1000) # warmuptimes= []
for_inrange(runs):
t0=time.perf_counter()
func(n)
times.append(time.perf_counter() -t0)
avg=sum(times) /len(times)
rate=n/avg/1e6print(f" {name:25s}{avg:.3f}s ({rate:.1f}M iter/s)")
returnavgif__name__=="__main__":
jit="JIT"ifos.environ.get("PYTHON_JIT", "1") =="1"else"no JIT"print(f"Python {sys.version.split()[0]} ({jit}), avg of 3 runs\n")
run_avg("dict_get[str]", bench_get_str, N)
run_avg("dict_get[int]", bench_get_int, N)
run_avg("dict_get[float]", bench_get_float, N)
run_avg("dict_get[complex]", bench_get_complex, N)
run_avg("dict_store[str]", bench_store_str, N)
run_avg("dict_store[int]", bench_store_int, N)
run_avg("dict_store[float]", bench_store_float, N)
run_avg("dict_store[complex]",bench_store_complex,N)
run_avg("dict_get[multi]", bench_get_multi, N//4)
run_avg("dict_get[obj]", bench_get_obj, N)
run_avg("dict_store[obj]", bench_store_obj, N)

Comment threadPython/optimizer_bytecodes.c Outdated
@kumaraditya303
kumaraditya303 enabled auto-merge (squash) March 30, 2026 13:41
@kumaraditya303
kumaraditya303 enabled auto-merge (squash) March 30, 2026 13:41
@kumaraditya303
kumaraditya303 merged commit 8e9d21c into python:mainMar 30, 2026
117 of 119 checks passed
@kumaraditya303
kumaraditya303 deleted the jit-hash branch March 30, 2026 14:23
maurycy added a commit to maurycy/cpython that referenced this pull request Mar 30, 2026
…ktor
* upstream/main: (119 commits)
pythongh-144270: Make SubElement parent and tag positional-only (pythonGH-144845)
pythongh-146558: JIT optimize dict access for objects with known hash (python#146559)
pythongh-139922: always run MSVC 64-bit tail-calling CI (pythonGH-146570)
pythongh-126835: Fix _PY_IS_SMALL_INT() macro (python#146631)
pythongh-146587: fix type slot assignment incase of multiple slots for same name (python#146593)
pythongh-138122: Add differential flame graph (python#145785)
pythongh-146416: Emscripten: Improve standard stream handling in node_entry.mjs (python#146417)
pythongh-146444: Don't package as part of iOS 'build hosts' target (python#146628)
pythongh-138850: Add --disable-epoll to configure (pythonGH-145768)
pythongh-146444: Make Platforms/Apple/ compatible with Python 3.9 (python#146624)
pythongh-138577: Fix keyboard shortcuts in getpass with echo_char (python#141597)
pythongh-146556: Fix infinite loop in annotationlib.get_annotations() on circular __wrapped__ (python#146557)
pythongh-146579: _zstd: Fix decompression options dict error message (python#146577)
pythongh-146083: Upgrade bundled Expat to 2.7.5 (python#146085)
pythongh-146080: fix a crash in SNI callbacks when the SSL object is gone (python#146573)
pythongh-146090: fix memory management of internal `sqlite3` callback contexts (python#146569)
pythongh-145876: Do not mask KeyErrors raised during dictionary unpacking in call (pythonGH-146472)
pythongh-146004: fix test_args_from_interpreter_flags on windows (python#146580)
pythongh-139003: Use frozenset for module level attributes in _pyrepl.utils (python#139004)
pythonGH-146527: Add more data to GC statistics and add it to PyDebugOffsets (python#146532)
...
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@kumaraditya303@Fidget-Spinner@savannahostrowski