◐ Shell
clean mode source ↗

gh-146558: optimize dict access with objects with known hash by kumaraditya303 · Pull Request #146559 · python/cpython

"""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
"""

import time
import os
import sys

N = 50_000_000


def bench_get_str(n):
    d = {'a': 1, 'b': 2, 'c': 3}
    x = 0
    for _ in range(n):
        x += d['a']
    return x


def bench_get_int(n):
    d = {1: 10, 2: 20, 3: 30}
    x = 0
    for _ in range(n):
        x += d[1]
    return x


def bench_store_str(n):
    d = {}
    for _ in range(n):
        d['a'] = 1
    return d


def bench_store_int(n):
    d = {}
    for _ in range(n):
        d[1] = 1
    return d


def bench_get_float(n):
    d = {1.5: 1, 2.5: 2, 3.5: 3}
    x = 0
    for _ in range(n):
        x += d[1.5]
    return x


def bench_store_float(n):
    d = {}
    for _ in range(n):
        d[1.5] = 1
    return d


def bench_get_complex(n):
    d = {1+2j: 1, 3+4j: 2}
    x = 0
    for _ in range(n):
        x += d[1+2j]
    return x


def bench_store_complex(n):
    d = {}
    for _ in range(n):
        d[1+2j] = 1
    return d


def bench_get_multi(n):
    d = {'a': 1, 1: 2, b'x': 3, (1, 2): 4}
    x = 0
    for _ in range(n):
        x += d['a'] + d[1] + d[b'x'] + d[(1, 2)]
    return x


class _Key:
    pass

_KEY = _Key()


def bench_get_obj(n):
    d = {_KEY: 1}
    x = 0
    for _ in range(n):
        x += d[_KEY]
    return x


def bench_store_obj(n):
    d = {}
    for _ in range(n):
        d[_KEY] = 1
    return d


def run_avg(name, func, n, runs=3):
    func(1000)  # warmup
    times = []
    for _ in range(runs):
        t0 = time.perf_counter()
        func(n)
        times.append(time.perf_counter() - t0)
    avg = sum(times) / len(times)
    rate = n / avg / 1e6
    print(f"  {name:25s}  {avg:.3f}s  ({rate:.1f}M iter/s)")
    return avg


if __name__ == "__main__":
    jit = "JIT" if os.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)