Skip to content

Commit 0976e4a

Browse files
authored
Add bm_base64 covering common base64 module APIs (#447)
* Add bm_base64 covering common base64 module APIs * add docs and manifest entry * version 1.14.0 * merge into a smaller # of benchmarks by size * Balance encode and decodes, remove validate=True, cover str input. per review from Serhiy. * update docs
1 parent a2b83b9 commit 0976e4a

File tree

7 files changed

+320
-2
lines changed

7 files changed

+320
-2
lines changed

doc/benchmarks.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,32 @@ These benchmarks also have an "eager" flavor that uses asyncio eager task factor
7676
if available.
7777

7878

79+
base64
80+
------
81+
82+
Benchmark the ``base64`` module's encoding and decoding functions. Each
83+
algorithm has ``_small`` and ``_large`` variants that test both encode and
84+
decode in a single benchmark:
85+
86+
* ``_small``: Balanced iterations across 20B, 127B, 3KiB, and 9KB data sizes
87+
more likely to show the impact of overhead.
88+
* ``_large``: Large data focus with 100KiB and ~1MiB data sizes likely to
89+
demonstrate implementation efficiency.
90+
91+
Available benchmarks:
92+
93+
* ``base64_small``, ``base64_large``: Standard Base64 encoding and decoding
94+
* ``urlsafe_base64_small``: URL-safe Base64 (small only, as URLs shouldn't
95+
contain huge data)
96+
* ``base32_small``, ``base32_large``: Base32 encoding and decoding
97+
* ``base16_small``, ``base16_large``: Base16/hex encoding and decoding
98+
* ``ascii85_small``, ``ascii85_large``: Ascii85 encoding and decoding
99+
(includes ``wrapcol=76`` code path)
100+
* ``base85_small``, ``base85_large``: Base85 encoding and decoding
101+
102+
See the `base64 module <https://docs.python.org/dev/library/base64.html>`_.
103+
104+
79105
chameleon
80106
---------
81107

doc/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changelog
22
=========
33

4+
Version 1.14.0
5+
--------------
6+
* Add base64 module benchmark (b64, b32, b16, a85, b85)
7+
48
Version 1.13.0 (2025-10-27)
59
--------------
610
* Re-enable xdsl benchmark

doc/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
# built documents.
5656
#
5757
# The short X.Y version.
58-
version = release = "1.0.6"
58+
version = release = "1.14.0"
5959

6060
# The language for content autogenerated by Sphinx. Refer to documentation
6161
# for a list of supported languages.

pyperformance/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
from importlib.metadata import distribution
55

6-
VERSION = (1, 13, 0)
6+
VERSION = (1, 14, 0)
77
__version__ = ".".join(map(str, VERSION))
88

99

pyperformance/data-files/benchmarks/MANIFEST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ async_tree_eager_memoization_tg <local:async_tree>
2424
asyncio_tcp <local>
2525
asyncio_tcp_ssl <local:asyncio_tcp>
2626
asyncio_websockets <local>
27+
base64 <local>
2728
bpe_tokeniser <local>
2829
concurrent_imap <local>
2930
coroutines <local>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[project]
2+
name = "pyperformance_bm_base64"
3+
requires-python = ">=3.8"
4+
dependencies = ["pyperf"]
5+
urls = {repository = "https://github.com/python/pyperformance"}
6+
dynamic = ["version"]
7+
8+
[tool.pyperformance]
9+
name = "base64"
10+
tags = "serialize"
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
"""Benchmark for the base64 module's primary public APIs.
2+
3+
Tests encoding and decoding performance across various variants
4+
and data sizes, split into _small (balanced small data) and _large variants.
5+
6+
Small weighs towards measuring overhead, large measures the core algorithm
7+
loop implementation.
8+
"""
9+
10+
import base64
11+
import random
12+
import pyperf
13+
14+
15+
# Generate test data with a fixed seed for reproducibility
16+
random.seed(12345)
17+
DATA_TINY = random.randbytes(20)
18+
DATA_SMALL = random.randbytes(127) # odd on purpose
19+
DATA_MEDIUM = random.randbytes(3072)
20+
DATA_9K = random.randbytes(9000)
21+
DATA_LARGE = random.randbytes(102400)
22+
DATA_HUGE = random.randbytes(1048574) # 1M-2
23+
24+
# Pre-encoded data for decode benchmarks
25+
B64_TINY = base64.b64encode(DATA_TINY)
26+
B64_SMALL = base64.b64encode(DATA_SMALL)
27+
B64_MEDIUM = base64.b64encode(DATA_MEDIUM)
28+
B64_9K_STR = base64.b64encode(DATA_9K).decode('ascii')
29+
B64_LARGE = base64.b64encode(DATA_LARGE)
30+
B64_HUGE = base64.b64encode(DATA_HUGE)
31+
32+
B64_URLSAFE_TINY = base64.urlsafe_b64encode(DATA_TINY)
33+
B64_URLSAFE_SMALL = base64.urlsafe_b64encode(DATA_SMALL)
34+
B64_URLSAFE_MEDIUM = base64.urlsafe_b64encode(DATA_MEDIUM)
35+
B64_URLSAFE_9K_STR = base64.urlsafe_b64encode(DATA_9K).decode('ascii')
36+
37+
B32_TINY = base64.b32encode(DATA_TINY)
38+
B32_SMALL = base64.b32encode(DATA_SMALL)
39+
B32_MEDIUM = base64.b32encode(DATA_MEDIUM)
40+
B32_9K_STR = base64.b32encode(DATA_9K).decode('ascii')
41+
B32_LARGE = base64.b32encode(DATA_LARGE)
42+
B32_HUGE = base64.b32encode(DATA_HUGE)
43+
44+
B16_TINY = base64.b16encode(DATA_TINY)
45+
B16_SMALL = base64.b16encode(DATA_SMALL)
46+
B16_MEDIUM = base64.b16encode(DATA_MEDIUM)
47+
B16_9K_STR = base64.b16encode(DATA_9K).decode('ascii')
48+
B16_LARGE = base64.b16encode(DATA_LARGE)
49+
B16_HUGE = base64.b16encode(DATA_HUGE)
50+
51+
A85_TINY = base64.a85encode(DATA_TINY)
52+
A85_SMALL = base64.a85encode(DATA_SMALL)
53+
A85_MEDIUM = base64.a85encode(DATA_MEDIUM)
54+
A85_9K_STR = base64.a85encode(DATA_9K).decode('ascii')
55+
A85_LARGE = base64.a85encode(DATA_LARGE)
56+
A85_HUGE = base64.a85encode(DATA_HUGE)
57+
58+
B85_TINY = base64.b85encode(DATA_TINY)
59+
B85_SMALL = base64.b85encode(DATA_SMALL)
60+
B85_MEDIUM = base64.b85encode(DATA_MEDIUM)
61+
B85_9K_STR = base64.b85encode(DATA_9K).decode('ascii')
62+
B85_LARGE = base64.b85encode(DATA_LARGE)
63+
B85_HUGE = base64.b85encode(DATA_HUGE)
64+
65+
66+
# --- Base64 ---
67+
68+
def bench_b64_small(loops):
69+
range_it = range(loops)
70+
t0 = pyperf.perf_counter()
71+
for _ in range_it:
72+
for _ in range(450):
73+
base64.b64encode(DATA_TINY)
74+
base64.b64decode(B64_TINY)
75+
for _ in range(71):
76+
base64.b64encode(DATA_SMALL)
77+
base64.b64decode(B64_SMALL)
78+
for _ in range(3):
79+
base64.b64encode(DATA_MEDIUM)
80+
base64.b64decode(B64_MEDIUM)
81+
base64.b64encode(DATA_9K)
82+
base64.b64decode(B64_9K_STR)
83+
return pyperf.perf_counter() - t0
84+
85+
86+
def bench_b64_large(loops):
87+
range_it = range(loops)
88+
t0 = pyperf.perf_counter()
89+
for _ in range_it:
90+
for _ in range(10):
91+
base64.b64encode(DATA_LARGE)
92+
base64.b64decode(B64_LARGE)
93+
base64.b64encode(DATA_HUGE)
94+
base64.b64decode(B64_HUGE)
95+
return pyperf.perf_counter() - t0
96+
97+
98+
# --- URL-safe Base64 (small only) ---
99+
100+
def bench_urlsafe_b64_small(loops):
101+
range_it = range(loops)
102+
t0 = pyperf.perf_counter()
103+
for _ in range_it:
104+
for _ in range(450):
105+
base64.urlsafe_b64encode(DATA_TINY)
106+
base64.urlsafe_b64decode(B64_URLSAFE_TINY)
107+
for _ in range(71):
108+
base64.urlsafe_b64encode(DATA_SMALL)
109+
base64.urlsafe_b64decode(B64_URLSAFE_SMALL)
110+
for _ in range(3):
111+
base64.urlsafe_b64encode(DATA_MEDIUM)
112+
base64.urlsafe_b64decode(B64_URLSAFE_MEDIUM)
113+
base64.urlsafe_b64encode(DATA_9K)
114+
base64.urlsafe_b64decode(B64_URLSAFE_9K_STR)
115+
return pyperf.perf_counter() - t0
116+
117+
118+
# --- Base32 ---
119+
120+
def bench_b32_small(loops):
121+
range_it = range(loops)
122+
t0 = pyperf.perf_counter()
123+
for _ in range_it:
124+
for _ in range(450):
125+
base64.b32encode(DATA_TINY)
126+
base64.b32decode(B32_TINY)
127+
for _ in range(71):
128+
base64.b32encode(DATA_SMALL)
129+
base64.b32decode(B32_SMALL)
130+
for _ in range(3):
131+
base64.b32encode(DATA_MEDIUM)
132+
base64.b32decode(B32_MEDIUM)
133+
base64.b32encode(DATA_9K)
134+
base64.b32decode(B32_9K_STR)
135+
return pyperf.perf_counter() - t0
136+
137+
138+
def bench_b32_large(loops):
139+
range_it = range(loops)
140+
t0 = pyperf.perf_counter()
141+
for _ in range_it:
142+
for _ in range(10):
143+
base64.b32encode(DATA_LARGE)
144+
base64.b32decode(B32_LARGE)
145+
base64.b32encode(DATA_HUGE)
146+
base64.b32decode(B32_HUGE)
147+
return pyperf.perf_counter() - t0
148+
149+
150+
# --- Base16 ---
151+
152+
def bench_b16_small(loops):
153+
range_it = range(loops)
154+
t0 = pyperf.perf_counter()
155+
for _ in range_it:
156+
for _ in range(450):
157+
base64.b16encode(DATA_TINY)
158+
base64.b16decode(B16_TINY)
159+
for _ in range(71):
160+
base64.b16encode(DATA_SMALL)
161+
base64.b16decode(B16_SMALL)
162+
for _ in range(3):
163+
base64.b16encode(DATA_MEDIUM)
164+
base64.b16decode(B16_MEDIUM)
165+
base64.b16encode(DATA_9K)
166+
base64.b16decode(B16_9K_STR)
167+
return pyperf.perf_counter() - t0
168+
169+
170+
def bench_b16_large(loops):
171+
range_it = range(loops)
172+
t0 = pyperf.perf_counter()
173+
for _ in range_it:
174+
for _ in range(10):
175+
base64.b16encode(DATA_LARGE)
176+
base64.b16decode(B16_LARGE)
177+
base64.b16encode(DATA_HUGE)
178+
base64.b16decode(B16_HUGE)
179+
return pyperf.perf_counter() - t0
180+
181+
182+
# --- Ascii85 (includes wrapcol=76) ---
183+
184+
def bench_a85_small(loops):
185+
range_it = range(loops)
186+
t0 = pyperf.perf_counter()
187+
for _ in range_it:
188+
for _ in range(450):
189+
base64.a85encode(DATA_TINY)
190+
base64.a85encode(DATA_TINY, wrapcol=76)
191+
base64.a85decode(A85_TINY)
192+
base64.a85decode(A85_TINY) # balance enc+dec weight
193+
for _ in range(71):
194+
base64.a85encode(DATA_SMALL)
195+
base64.a85encode(DATA_SMALL, wrapcol=76)
196+
base64.a85decode(A85_SMALL)
197+
base64.a85decode(A85_SMALL) # balance enc+dec weight
198+
for _ in range(3):
199+
base64.a85encode(DATA_MEDIUM)
200+
base64.a85encode(DATA_MEDIUM, wrapcol=76)
201+
base64.a85decode(A85_MEDIUM)
202+
base64.a85decode(A85_MEDIUM) # balance enc+dec weight
203+
base64.a85encode(DATA_9K)
204+
base64.a85encode(DATA_9K, wrapcol=76)
205+
base64.a85decode(A85_9K_STR)
206+
base64.a85decode(A85_9K_STR) # balance enc+dec weight
207+
return pyperf.perf_counter() - t0
208+
209+
210+
def bench_a85_large(loops):
211+
range_it = range(loops)
212+
t0 = pyperf.perf_counter()
213+
for _ in range_it:
214+
for _ in range(10):
215+
base64.a85encode(DATA_LARGE)
216+
base64.a85encode(DATA_LARGE, wrapcol=76)
217+
base64.a85decode(A85_LARGE)
218+
base64.a85decode(A85_LARGE) # balance enc+dec weight
219+
base64.a85encode(DATA_HUGE)
220+
base64.a85encode(DATA_HUGE, wrapcol=76)
221+
base64.a85decode(A85_HUGE)
222+
base64.a85decode(A85_HUGE) # balance enc+dec weight
223+
return pyperf.perf_counter() - t0
224+
225+
226+
# --- Base85 ---
227+
228+
def bench_b85_small(loops):
229+
range_it = range(loops)
230+
t0 = pyperf.perf_counter()
231+
for _ in range_it:
232+
for _ in range(450):
233+
base64.b85encode(DATA_TINY)
234+
base64.b85decode(B85_TINY)
235+
for _ in range(71):
236+
base64.b85encode(DATA_SMALL)
237+
base64.b85decode(B85_SMALL)
238+
for _ in range(3):
239+
base64.b85encode(DATA_MEDIUM)
240+
base64.b85decode(B85_MEDIUM)
241+
base64.b85encode(DATA_9K)
242+
base64.b85decode(B85_9K_STR)
243+
return pyperf.perf_counter() - t0
244+
245+
246+
def bench_b85_large(loops):
247+
range_it = range(loops)
248+
t0 = pyperf.perf_counter()
249+
for _ in range_it:
250+
for _ in range(10):
251+
base64.b85encode(DATA_LARGE)
252+
base64.b85decode(B85_LARGE)
253+
base64.b85encode(DATA_HUGE)
254+
base64.b85decode(B85_HUGE)
255+
return pyperf.perf_counter() - t0
256+
257+
258+
if __name__ == "__main__":
259+
runner = pyperf.Runner()
260+
runner.metadata['description'] = "Benchmark base64 module encoding/decoding"
261+
262+
runner.bench_time_func('base64_small', bench_b64_small)
263+
runner.bench_time_func('base64_large', bench_b64_large)
264+
265+
runner.bench_time_func('urlsafe_base64_small', bench_urlsafe_b64_small)
266+
267+
runner.bench_time_func('base32_small', bench_b32_small)
268+
runner.bench_time_func('base32_large', bench_b32_large)
269+
270+
runner.bench_time_func('base16_small', bench_b16_small)
271+
runner.bench_time_func('base16_large', bench_b16_large)
272+
273+
runner.bench_time_func('ascii85_small', bench_a85_small)
274+
runner.bench_time_func('ascii85_large', bench_a85_large)
275+
276+
runner.bench_time_func('base85_small', bench_b85_small)
277+
runner.bench_time_func('base85_large', bench_b85_large)

0 commit comments

Comments
 (0)