Skip to content

Commit 12d4af3

Browse files
committed
benchmark for _PyDict_NewPresized
See python/cpython#139773
1 parent e703484 commit 12d4af3

9 files changed

Lines changed: 146 additions & 0 deletions

File tree

‎2025/dictnew/README.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Python 3.5
2+
3+
| Benchmark | dict_new | dict_presized |
4+
|----------------|:--------:|:---------------------:|
5+
| dict-1 | 454 ns | 398 ns: 1.14x faster |
6+
| dict-5 | 2.07 us | 1.73 us: 1.20x faster |
7+
| dict-10 | 3.96 us | 3.28 us: 1.21x faster |
8+
| dict-25 | 9.25 us | 7.56 us: 1.22x faster |
9+
| dict-100 | 31.4 us | 25.6 us: 1.22x faster |
10+
| dict-500 | 113 us | 103 us: 1.11x faster |
11+
| dict-1,000 | 218 us | 200 us: 1.09x faster |
12+
| Geometric mean | (ref) | 1.17x faster |
13+
14+
Python 3.12
15+
16+
| Benchmark | dict_new | dict_presized |
17+
|----------------|:--------:|:---------------------:|
18+
| dict-1 | 378 ns | 337 ns: 1.12x faster |
19+
| dict-5 | 1.49 us | 1.34 us: 1.11x faster |
20+
| dict-10 | 2.65 us | 2.32 us: 1.14x faster |
21+
| dict-25 | 5.84 us | 5.12 us: 1.14x faster |
22+
| dict-100 | 22.0 us | 18.0 us: 1.22x faster |
23+
| dict-500 | 98.6 us | 87.6 us: 1.13x faster |
24+
| dict-1,000 | 194 us | 172 us: 1.13x faster |
25+
| Geometric mean | (ref) | 1.14x faster |
26+

‎2025/dictnew/bench_new.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pyperf
2+
import dictbench
3+
4+
runner = pyperf.Runner()
5+
for size in (1, 5, 10, 25, 100, 500, 1000):
6+
runner.bench_time_func('dict-{:,}'.format(size), dictbench.bench_new, size)

‎2025/dictnew/bench_presized.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pyperf
2+
import dictbench
3+
4+
runner = pyperf.Runner()
5+
for size in (1, 5, 10, 25, 100, 500, 1000):
6+
runner.bench_time_func('dict-{:,}'.format(size), dictbench.bench_presized, size)

‎2025/dictnew/dict_new.json‎

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

‎2025/dictnew/dict_new_312.json‎

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

‎2025/dictnew/dict_presized.json‎

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

‎2025/dictnew/dict_presized_312.json‎

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

‎2025/dictnew/dictbench.c‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#define PY_SSIZE_T_CLEAN
2+
#include <Python.h>
3+
#include <time.h>
4+
5+
static PyObject *bench_dict_new(PyObject *ob, PyObject *args) {
6+
Py_ssize_t size, loops;
7+
if (!PyArg_ParseTuple(args, "nn", &size, &loops)) {
8+
return NULL;
9+
}
10+
11+
struct timespec start, end;
12+
clock_gettime(CLOCK_MONOTONIC, &start);
13+
14+
for (Py_ssize_t loop = 0; loop < loops; loop++) {
15+
PyObject *d = PyDict_New();
16+
if (d == NULL) {
17+
return NULL;
18+
}
19+
20+
for (Py_ssize_t i = 0; i < size; i++) {
21+
PyObject *key = PyUnicode_FromFormat("%zi", i);
22+
assert(key != NULL);
23+
24+
PyObject *value = PyLong_FromLong(i);
25+
assert(value != NULL);
26+
27+
PyDict_SetItem(d, key, value);
28+
Py_DECREF(key);
29+
Py_DECREF(value);
30+
}
31+
32+
assert(PyDict_Size(d) == size);
33+
Py_DECREF(d);
34+
}
35+
36+
clock_gettime(CLOCK_MONOTONIC, &end);
37+
double time_spent = (end.tv_sec - start.tv_sec) +
38+
(end.tv_nsec - start.tv_nsec) / 1000000000.0;
39+
return PyFloat_FromDouble(time_spent);
40+
}
41+
42+
static PyObject *bench_dict_presized(PyObject *ob, PyObject *args) {
43+
Py_ssize_t size, loops;
44+
if (!PyArg_ParseTuple(args, "nn", &size, &loops)) {
45+
return NULL;
46+
}
47+
48+
struct timespec start, end;
49+
clock_gettime(CLOCK_MONOTONIC, &start);
50+
51+
for (Py_ssize_t loop = 0; loop < loops; loop++) {
52+
PyObject *d = _PyDict_NewPresized(size);
53+
if (d == NULL) {
54+
return NULL;
55+
}
56+
57+
for (Py_ssize_t i = 0; i < size; i++) {
58+
PyObject *key = PyUnicode_FromFormat("%zi", i);
59+
assert(key != NULL);
60+
61+
PyObject *value = PyLong_FromLong(i);
62+
assert(value != NULL);
63+
64+
PyDict_SetItem(d, key, value);
65+
Py_DECREF(key);
66+
Py_DECREF(value);
67+
}
68+
69+
assert(PyDict_Size(d) == size);
70+
Py_DECREF(d);
71+
}
72+
73+
clock_gettime(CLOCK_MONOTONIC, &end);
74+
double time_spent = (end.tv_sec - start.tv_sec) +
75+
(end.tv_nsec - start.tv_nsec) / 1000000000.0;
76+
return PyFloat_FromDouble(time_spent);
77+
}
78+
79+
static PyMethodDef DictBenchMethods[] = {
80+
{"bench_new", bench_dict_new, METH_VARARGS, "Benchmark PyDict_New."},
81+
{"bench_presized", bench_dict_presized, METH_VARARGS,
82+
"Benchmark _PyDict_NewPresized."},
83+
{NULL, NULL, 0, NULL} /* Sentinel */
84+
};
85+
86+
static struct PyModuleDef dictbenchmodule = {
87+
PyModuleDef_HEAD_INIT, "dictbench", /* name of module */
88+
NULL, /* module documentation, may be NULL */
89+
-1, /* size of per-interpreter state of the module,
90+
or -1 if the module keeps state in global variables. */
91+
DictBenchMethods};
92+
93+
PyMODINIT_FUNC PyInit_dictbench(void) {
94+
return PyModule_Create(&dictbenchmodule);
95+
}

‎2025/dictnew/setup.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from setuptools import setup, Extension
2+
3+
module1 = Extension('dictbench',
4+
sources = ['dictbench.c'])
5+
6+
setup (name = 'DictBench',
7+
version = '1.0',
8+
description = 'Microbenchmark for dict creation',
9+
ext_modules = [module1])

0 commit comments

Comments
 (0)