Skip to content

Commit 0b23560

Browse files
committed
Add benchmarking info
1 parent 753461e commit 0b23560

File tree

3 files changed

+277
-0
lines changed

3 files changed

+277
-0
lines changed

BENCHMARK.rst

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
============
2+
Benchmarking
3+
============
4+
5+
As of Mar 2026, there are 2 alternative implementations of SonyFlake in
6+
Python:
7+
8+
* `hjpotter92/sonyflake-py <https://github.com/hjpotter92/sonyflake-py>`_
9+
* `iyad-f/sonyflake <https://github.com/iyad-f/sonyflake>`_
10+
11+
None of them has batch generation feature, neither they use extension modules
12+
for speed ups. So we'll be comparing against pure-Python, single-id generation
13+
mode in ``sonyflake-turbo``:
14+
15+
.. code-block:: python
16+
17+
sf = SonyFlake(*range(0xFFFF))
18+
asf = AsyncSonyFlake(sf)
19+
next(sf) # for sync benchmarks
20+
await asf # for async benchmarks
21+
22+
Since Sonyflake algorithm at its core has limited throughput, we'll be using
23+
as much machine ids as possible. For ``sonyflake-turbo`` it is as trivial
24+
as:
25+
26+
.. code-block:: python
27+
28+
sf = SonyFlake(*range(0xFFFF))
29+
30+
for other implementation we'll be using something equivalent to:
31+
32+
.. code-block:: python
33+
34+
sfs = [
35+
SonyFlake(machine_id=machine_id).next_id
36+
for machine_id in range(0xFFFF)
37+
]
38+
max_i = len(sfs)
39+
i = 0
40+
41+
def next_id() -> int:
42+
nonlocal i
43+
id_ = sfs[i]()
44+
i = (i + 1) % max_i
45+
return id_
46+
47+
48+
In order to shave-off some time for ``getattr``-ing of ``next_id`` we do it
49+
in initialization step. Same goes for ``len``-s. We're not using generators,
50+
since ``yield``-s are also tanking performance.
51+
52+
This setup is closest to "running multiple generators in parallel".
53+
54+
Unfortunately, both ``hjpotter92/sonyflake-py`` and ``iyad-f/sonyflake``
55+
share the same package name ``sonyflake``, so we cannot benchmark everything
56+
in one go. Results has to be merged later.
57+
58+
Full source code of benchmark is located in ``benchmark.py`` file in the root
59+
of the repo.
60+
61+
Naming breakdown:
62+
63+
* ``sonyflake_py`` - ``hjpotter92/sonyflake-py``
64+
* ``sonyflake_alt*`` - ``iyad-f/sonyflake``
65+
* ``turbo_native*`` - running against ``sonyflake_turbo._sonyflake.SonyFlake``
66+
* ``turbo_pure*`` - running against ``sonyflake_turbo.pure.SonyFlake``
67+
* ``turbo_*_solo`` - solo mode (``next(sf)``, ``await asf``)
68+
* ``turbo_*_batch`` - batch mode (``sf(1000)``, ``await asf(1000)``)
69+
70+
Run:
71+
72+
.. code-block:: sh
73+
74+
python benchmark.py
75+
76+
Results
77+
=======
78+
79+
For CPython 3.12.3 on the Intel Xeon E3-1275, results are following (lower %
80+
= better):
81+
82+
.. csv-table:: Sync
83+
:header: "Name", "Time", "%"
84+
85+
turbo_native_batch,0.03s,3.03%
86+
turbo_native_solo,0.13s,13.13%
87+
turbo_pure_batch,0.35s,35.35%
88+
turbo_pure_solo,0.99s,100.00%
89+
sonyflake_py,1.35s,136.36%
90+
sonyflake_alt,2.48s,250.51%
91+
92+
.. csv-table:: Async
93+
:header: "Name", "Time", "%"
94+
95+
turbo_native_batch_async,0.05s,0.41%
96+
turbo_native_solo_async,10.16s,83.07%
97+
turbo_pure_batch_async,0.31s,2.53%
98+
turbo_pure_solo_async,12.23s,100.00%
99+
sonyflake_alt_async,14.07s,115.04%

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ Otherwise you might want to use it for one of the following reasons:
247247
.. _UUIDv7: https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7
248248
.. _IDOR: https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html
249249

250+
Benchmarking
251+
~~~~~~~~~~~~
252+
253+
See the `benchmarks <BENCHMARK.rst>`_ for performance details.
254+
250255
Development
251256
===========
252257

benchmark.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#!/usr/bin/env python3
2+
import platform
3+
import sys
4+
from asyncio import new_event_loop, set_event_loop
5+
from contextlib import suppress
6+
from datetime import datetime, timezone
7+
from timeit import timeit
8+
9+
from sonyflake_turbo import AsyncSonyFlake
10+
11+
EPOCH = 1749081600
12+
EPOCH_DT = datetime.fromtimestamp(EPOCH, tz=timezone.utc)
13+
ONE_THOUSAND = 1000
14+
ONE_MILLION = ONE_THOUSAND * ONE_THOUSAND
15+
# Benchmarking a single id generator is not useful since it'll be bottlenecked
16+
# by SonyFlake's own throughtput. Use all possible machine ids to alleviate
17+
# stray sleeps.
18+
MACHINE_IDS = range(0xFFFF)
19+
# `asyncio.run()` creates new event loop on every invocation, which is no-go
20+
# for benchmarking. Reuse the same event loop for all benchmarks.
21+
loop = new_event_loop()
22+
set_event_loop(loop)
23+
24+
25+
def _turbo(name, sf):
26+
t_batch = timeit(lambda: sf(ONE_THOUSAND), number=ONE_THOUSAND)
27+
t_solo = timeit(lambda: next(sf), number=ONE_MILLION)
28+
29+
print(f"turbo_{name}_batch,{t_batch:.2f}")
30+
print(f"turbo_{name}_solo,{t_solo:.2f}")
31+
32+
33+
def _turbo_async(name, asf):
34+
def batch():
35+
loop.run_until_complete(asf(ONE_THOUSAND))
36+
37+
def solo():
38+
loop.run_until_complete(asf)
39+
40+
t_batch = timeit(batch, number=ONE_THOUSAND)
41+
t_solo = timeit(solo, number=ONE_MILLION)
42+
43+
print(f"turbo_{name}_batch_async,{t_batch:.2f}")
44+
print(f"turbo_{name}_solo_async,{t_solo:.2f}")
45+
46+
47+
def turbo_native():
48+
from sonyflake_turbo._sonyflake import SonyFlake
49+
50+
_turbo("native", SonyFlake(*MACHINE_IDS, start_time=EPOCH))
51+
52+
53+
def turbo_pure():
54+
from sonyflake_turbo.pure import SonyFlake
55+
56+
_turbo("pure", SonyFlake(*MACHINE_IDS, start_time=EPOCH))
57+
58+
59+
def turbo_async_native():
60+
from sonyflake_turbo._sonyflake import SonyFlake
61+
62+
asf = AsyncSonyFlake(SonyFlake(*MACHINE_IDS, start_time=EPOCH))
63+
64+
_turbo_async("native", asf)
65+
66+
67+
def turbo_async_pure():
68+
from sonyflake_turbo.pure import SonyFlake
69+
70+
asf = AsyncSonyFlake(SonyFlake(*MACHINE_IDS, start_time=EPOCH))
71+
72+
_turbo_async("pure", asf)
73+
74+
75+
def sonyflake_py():
76+
"""pip install sonyflake-py"""
77+
78+
from sonyflake import SonyFlake
79+
80+
sfs = [
81+
SonyFlake(start_time=EPOCH_DT, machine_id=lambda: machine_id).next_id
82+
for machine_id in MACHINE_IDS
83+
]
84+
max_i = len(sfs)
85+
i = 0
86+
87+
def next_id() -> int:
88+
nonlocal i
89+
id_ = sfs[i]()
90+
i = (i + 1) % max_i
91+
return id_
92+
93+
t = timeit(next_id, number=ONE_MILLION)
94+
95+
print(f"sonyflake_py,{t:.2f}")
96+
97+
98+
def sonyflake_alt():
99+
"""pip install sonyflake"""
100+
101+
from sonyflake import Sonyflake
102+
103+
sfs = [
104+
Sonyflake(start_time=EPOCH_DT, machine_id=machine_id).next_id
105+
for machine_id in MACHINE_IDS
106+
]
107+
max_i = len(sfs)
108+
i = 0
109+
110+
def next_id() -> int:
111+
nonlocal i
112+
id_ = sfs[i]()
113+
i = (i + 1) % max_i
114+
return id_
115+
116+
t = timeit(next_id, number=ONE_MILLION)
117+
118+
print(f"sonyflake_alt,{t:.2f}")
119+
120+
121+
def sonyflake_alt_async():
122+
"""pip install sonyflake"""
123+
124+
from sonyflake import Sonyflake
125+
126+
sfs = [
127+
Sonyflake(start_time=EPOCH_DT, machine_id=machine_id).next_id_async
128+
for machine_id in MACHINE_IDS
129+
]
130+
max_i = len(sfs)
131+
i = 0
132+
133+
def next_id() -> int:
134+
nonlocal i
135+
id_ = loop.run_until_complete(sfs[i]())
136+
i = (i + 1) % max_i
137+
return id_
138+
139+
t = timeit(next_id, number=ONE_MILLION)
140+
141+
print(f"sonyflake_alt_async,{t:.2f}")
142+
143+
144+
def main():
145+
print(platform.python_implementation(), platform.python_version(), file=sys.stderr)
146+
for f in [
147+
turbo_native,
148+
turbo_pure,
149+
turbo_async_native,
150+
turbo_async_pure,
151+
sonyflake_py,
152+
sonyflake_alt,
153+
sonyflake_alt_async,
154+
]:
155+
with suppress(ImportError):
156+
f()
157+
print("done", file=sys.stderr)
158+
159+
160+
if __name__ == "__main__":
161+
main()
162+
# CPython 3.12.3
163+
# turbo_native_batch,0.03
164+
# turbo_native_solo,0.13
165+
# turbo_pure_batch,0.35
166+
# turbo_pure_solo,0.99
167+
# turbo_native_batch_async,0.05
168+
# turbo_native_solo_async,10.16
169+
# turbo_pure_batch_async,0.31
170+
# turbo_pure_solo_async,12.23
171+
# sonyflake_py,1.35
172+
# sonyflake_alt,2.48
173+
# sonyflake_alt_async,14.07

0 commit comments

Comments
 (0)