hashcodecs 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. hashcodecs-0.4.0/BENCHMARK.md +71 -0
  2. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/Cargo.lock +1 -1
  3. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/Cargo.toml +1 -1
  4. hashcodecs-0.4.0/PKG-INFO +190 -0
  5. hashcodecs-0.4.0/README.md +170 -0
  6. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/pyproject.toml +8 -1
  7. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/python/hashcodecs/__init__.py +12 -0
  8. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/python/hashcodecs/_hashcodecs.pyi +31 -4
  9. hashcodecs-0.4.0/python/hashcodecs/base64.py +89 -0
  10. hashcodecs-0.4.0/python/hashcodecs/base64.pyi +54 -0
  11. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/base64/aarch64.rs +44 -45
  12. hashcodecs-0.4.0/src/base64/x86.rs +822 -0
  13. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/base64/x86_avx512.rs +14 -39
  14. hashcodecs-0.4.0/src/python/base64/encode.rs +163 -0
  15. hashcodecs-0.4.0/src/python/base64.rs +792 -0
  16. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/python/buffer.rs +37 -12
  17. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/python/murmur3.rs +28 -49
  18. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/python.rs +5 -1
  19. hashcodecs-0.3.0/PKG-INFO +0 -202
  20. hashcodecs-0.3.0/README.md +0 -188
  21. hashcodecs-0.3.0/python/hashcodecs/base64.py +0 -59
  22. hashcodecs-0.3.0/python/hashcodecs/base64.pyi +0 -23
  23. hashcodecs-0.3.0/src/base64/x86.rs +0 -651
  24. hashcodecs-0.3.0/src/python/base64.rs +0 -335
  25. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/.gitignore +0 -0
  26. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/LICENSE +0 -0
  27. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/LICENSE-MIT +0 -0
  28. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/benches/base64.rs +0 -0
  29. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/benches/murmur3.rs +0 -0
  30. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/benches/support/mod.rs +0 -0
  31. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/build.rs +0 -0
  32. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/hatch_build.py +0 -0
  33. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/python/hashcodecs/murmur3.py +0 -0
  34. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/python/hashcodecs/py.typed +0 -0
  35. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/base64/dispatch.rs +0 -0
  36. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/base64.rs +0 -0
  37. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/lib.rs +0 -0
  38. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/murmur3/dispatch.rs +0 -0
  39. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/murmur3/x86.rs +0 -0
  40. {hashcodecs-0.3.0 → hashcodecs-0.4.0}/src/murmur3.rs +0 -0
@@ -0,0 +1,71 @@
1
+ # Benchmark Details
2
+
3
+ Environment: Windows 10 x64 and Intel Core Ultra 7 265K.
4
+
5
+ Conditions: clean builds, one pinned logical CPU, single-threaded execution, and 15 Python samples. Higher is better.
6
+
7
+ ## Reusable Python Buffers
8
+
9
+ | Alphabet | Input | Operation | hashcodecs |
10
+ | --- | --- | --- | ---: |
11
+ | Standard | 4 KiB | encode | **15.09 GiB/s** |
12
+ | | 4 KiB | decode | **18.43 GiB/s** |
13
+ | | 1 MiB | encode | **39.66 GiB/s** |
14
+ | | 1 MiB | decode | **29.03 GiB/s** |
15
+ | | 32 MiB | encode | **10.89 GiB/s** |
16
+ | | 32 MiB | decode | **10.36 GiB/s** |
17
+ | URL-safe | 4 KiB | encode | **14.83 GiB/s** |
18
+ | | 4 KiB | decode | **12.26 GiB/s** |
19
+ | | 1 MiB | encode | **39.71 GiB/s** |
20
+ | | 1 MiB | decode | **20.72 GiB/s** |
21
+ | | 32 MiB | encode | **10.84 GiB/s** |
22
+ | | 32 MiB | decode | **10.44 GiB/s** |
23
+
24
+ ## Python Base64 Batches
25
+
26
+ Each value is total input throughput; the parenthesized figure is items per second. Higher is better.
27
+
28
+ | Item | Batch | Operation | hashcodecs | hashcodecs loop | pybase64 loop | CPython loop |
29
+ | ---: | ---: | --- | ---: | ---: | ---: | ---: |
30
+ | 16 B | 8 | encode | **0.41 GiB/s (27.42M)** | 0.27 GiB/s (18.11M) | 0.13 GiB/s (8.45M) | 0.18 GiB/s (12.37M) |
31
+ | | | decode | **0.36 GiB/s (24.24M)** | 0.21 GiB/s (14.13M) | 0.08 GiB/s (5.44M) | 0.12 GiB/s (7.85M) |
32
+ | 16 B | 64 | encode | **0.55 GiB/s (36.69M)** | 0.31 GiB/s (20.81M) | 0.14 GiB/s (9.17M) | 0.21 GiB/s (13.81M) |
33
+ | | | decode | **0.49 GiB/s (32.86M)** | 0.24 GiB/s (16.13M) | 0.09 GiB/s (5.87M) | 0.13 GiB/s (8.63M) |
34
+ | 16 B | 1,024 | encode | **0.56 GiB/s (37.26M)** | 0.31 GiB/s (20.64M) | 0.14 GiB/s (9.07M) | 0.20 GiB/s (13.73M) |
35
+ | | | decode | **0.49 GiB/s (32.70M)** | 0.25 GiB/s (16.60M) | 0.09 GiB/s (5.95M) | 0.13 GiB/s (8.71M) |
36
+ | 256 B | 8 | encode | 1.77 GiB/s (7.43M) | 1.52 GiB/s (6.38M) | **1.93 GiB/s (8.08M)** | 0.40 GiB/s (1.67M) |
37
+ | | | decode | **5.03 GiB/s (21.12M)** | 3.02 GiB/s (12.66M) | 1.22 GiB/s (5.12M) | 0.76 GiB/s (3.19M) |
38
+ | 256 B | 64 | encode | 1.92 GiB/s (8.05M) | 1.62 GiB/s (6.82M) | **2.02 GiB/s (8.48M)** | 0.41 GiB/s (1.71M) |
39
+ | | | decode | **6.09 GiB/s (25.56M)** | 3.36 GiB/s (14.07M) | 1.29 GiB/s (5.40M) | 0.76 GiB/s (3.18M) |
40
+ | 256 B | 1,024 | encode | 1.92 GiB/s (8.05M) | 1.63 GiB/s (6.82M) | **1.99 GiB/s (8.35M)** | 0.40 GiB/s (1.69M) |
41
+ | | | decode | **5.87 GiB/s (24.61M)** | 3.30 GiB/s (13.83M) | 1.29 GiB/s (5.43M) | 0.76 GiB/s (3.17M) |
42
+ | 4 KiB | 8 | encode | **15.12 GiB/s (3.96M)** | 13.72 GiB/s (3.60M) | 12.69 GiB/s (3.33M) | 0.48 GiB/s (0.13M) |
43
+ | | | decode | **19.20 GiB/s (5.03M)** | 16.52 GiB/s (4.33M) | 8.29 GiB/s (2.17M) | 1.13 GiB/s (0.30M) |
44
+ | 4 KiB | 64 | encode | **12.59 GiB/s (3.30M)** | 11.55 GiB/s (3.03M) | 9.54 GiB/s (2.50M) | 0.48 GiB/s (0.12M) |
45
+ | | | decode | **18.95 GiB/s (4.97M)** | 16.12 GiB/s (4.23M) | 8.12 GiB/s (2.13M) | 1.12 GiB/s (0.29M) |
46
+ | 4 KiB | 1,024 | encode | 5.84 GiB/s (1.53M) | **8.81 GiB/s (2.31M)** | 7.83 GiB/s (2.05M) | 0.47 GiB/s (0.12M) |
47
+ | | | decode | 3.52 GiB/s (0.92M) | **3.81 GiB/s (1.00M)** | 2.99 GiB/s (0.79M) | 0.92 GiB/s (0.24M) |
48
+
49
+ ## Mutable Python Inputs
50
+
51
+ ### Base64
52
+
53
+ | Input | Operation | Returned `bytes` | Reusable `bytearray` |
54
+ | --- | --- | ---: | ---: |
55
+ | 4 KiB | encode | 14.12 GiB/s | **15.16 GiB/s** |
56
+ | | decode | 16.76 GiB/s | **18.27 GiB/s** |
57
+ | 1 MiB | encode | 2.88 GiB/s | **38.96 GiB/s** |
58
+ | | decode | 3.59 GiB/s | **28.99 GiB/s** |
59
+ | 32 MiB | encode | 2.92 GiB/s | **10.83 GiB/s** |
60
+ | | decode | 3.44 GiB/s | **10.35 GiB/s** |
61
+
62
+ ### MurmurHash3
63
+
64
+ | Variant | API | 4 KiB | 1 MiB | 32 MiB |
65
+ | --- | --- | ---: | ---: | ---: |
66
+ | x86 32-bit | one-shot | **3.79 GiB/s** | **3.98 GiB/s** | **3.97 GiB/s** |
67
+ | | incremental | **3.58 GiB/s** | **3.96 GiB/s** | **3.92 GiB/s** |
68
+ | x86 128-bit | one-shot | **8.09 GiB/s** | **9.22 GiB/s** | **9.12 GiB/s** |
69
+ | | incremental | **7.02 GiB/s** | **9.17 GiB/s** | **9.01 GiB/s** |
70
+ | x64 128-bit | one-shot | **8.68 GiB/s** | **10.10 GiB/s** | **9.54 GiB/s** |
71
+ | | incremental | **7.70 GiB/s** | **10.01 GiB/s** | **9.41 GiB/s** |
@@ -194,7 +194,7 @@ dependencies = [
194
194
 
195
195
  [[package]]
196
196
  name = "hashcodecs"
197
- version = "0.3.0"
197
+ version = "0.4.0"
198
198
  dependencies = [
199
199
  "base64",
200
200
  "base64-turbo",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "hashcodecs"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  edition = "2024"
5
5
  rust-version = "1.89"
6
6
  description = "SIMD-accelerated Base64 codecs and fast MurmurHash3 implementations"
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: hashcodecs
3
+ Version: 0.4.0
4
+ Summary: SIMD-accelerated Base64 and MurmurHash3 codecs
5
+ License-Expression: MIT OR Apache-2.0
6
+ License-File: LICENSE
7
+ License-File: LICENSE-MIT
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
16
+ Classifier: Programming Language :: Rust
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+
21
+ # hashcodecs
22
+
23
+ [![CI](https://img.shields.io/github/actions/workflow/status/kozistr/hashcodecs-rs/ci.yml?branch=main&style=for-the-badge&logo=github)](https://github.com/kozistr/hashcodecs-rs/actions/workflows/ci.yml)
24
+ [![PyPI](https://img.shields.io/pypi/v/hashcodecs?style=for-the-badge&logo=pypi)](https://pypi.org/project/hashcodecs/)
25
+ [![Python](https://img.shields.io/pypi/pyversions/hashcodecs?style=for-the-badge&logo=python)](https://pypi.org/project/hashcodecs/)
26
+ [![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-brightgreen?style=for-the-badge)](https://github.com/kozistr/hashcodecs-rs#license)
27
+ [![Downloads](https://img.shields.io/pypi/dm/hashcodecs?style=for-the-badge&label=downloads)](https://pypi.org/project/hashcodecs/)
28
+
29
+ `hashcodecs` provides runtime-dispatched SIMD Base64 codecs and fast, reference-compatible MurmurHash3 functions for Rust and Python.
30
+
31
+ ## Design
32
+
33
+ - Runtime-dispatched SIMD Base64 with portable scalar fallbacks.
34
+ - Reference-compatible MurmurHash3 with SIMD acceleration.
35
+ - Rust and Python `*_into` APIs for caller-managed output buffers.
36
+ - A familiar Python Base64 API, including native batch encode and decode operations.
37
+
38
+ ## Install
39
+
40
+ ```
41
+ pip3 install hashcodecs
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### Rust
47
+
48
+ ```rust
49
+ let encoded = hashcodecs::b64encode(b"hello");
50
+ assert_eq!(encoded, "aGVsbG8=");
51
+
52
+ let mut output = [0_u8; 8];
53
+ let written = hashcodecs::b64encode_into(b"hello", &mut output).unwrap();
54
+ assert_eq!(&output[..written], b"aGVsbG8=");
55
+ assert_eq!(hashcodecs::murmur3_x86_32(b"hello", 0), 0x248b_fa47);
56
+ ```
57
+
58
+ ### Python
59
+
60
+ ```python
61
+ import hashcodecs.base64 as base64
62
+ from hashcodecs import murmur3_32, murmur3_x64_128
63
+
64
+ assert base64.b64encode(b'hello') == b'aGVsbG8='
65
+ assert base64.b64decode(b'aGVsbG8=') == b'hello'
66
+ assert base64.b64encode_batch([b'hello', b'world']) == [b'aGVsbG8=', b'd29ybGQ=']
67
+ assert base64.b64decode_batch([b'aGVsbG8=', 'd29ybGQ=']) == [b'hello', b'world']
68
+ assert base64.b64encode(b'hello', padded=False) == b'aGVsbG8'
69
+ assert base64.b64decode(b'aGVsbG8', padded=False, canonical=True) == b'hello'
70
+ assert murmur3_32(b'hello') == 0x248BFA47
71
+
72
+ payload = b'hello'
73
+ encoded = bytearray(4 * ((len(payload) + 2) // 3))
74
+ encoded_len = base64.b64encode_into(payload, encoded)
75
+ decoded = bytearray(len(encoded))
76
+ decoded_len = base64.b64decode_into(encoded, decoded, validate=True)
77
+ assert encoded[:encoded_len] == b'aGVsbG8='
78
+ assert decoded[:decoded_len] == payload
79
+
80
+ hasher = murmur3_x64_128(seed=42)
81
+ hasher.update(b'hello')
82
+ assert hasher.hexdigest() == hasher.digest().hex()
83
+ ```
84
+
85
+ Build an installable wheel and source distribution with:
86
+
87
+ ```sh
88
+ uv build
89
+ ```
90
+
91
+ # Benchmark
92
+
93
+ Environment: Windows 10 x64 and Intel Core Ultra 7 265K.
94
+
95
+ Conditions: clean builds, one pinned logical CPU, single-threaded execution, 50 Rust samples, and 15 Python samples. Returned-output allocation is included except in the reusable-buffer table. Higher is better.
96
+
97
+ ## Run Locally
98
+
99
+ Comparison crates are development-only dependencies and are not included in consumer builds.
100
+
101
+ ```sh
102
+ cargo bench --bench base64
103
+ cargo bench --bench murmur3
104
+ uv sync --group benchmark --no-install-project
105
+ uv run --no-project --with . python benchmarks/python_base64.py
106
+ uv run --no-project --with . python benchmarks/python_base64.py --into
107
+ uv run --no-project --with . python benchmarks/python_base64.py --bytearray-input
108
+ uv run --no-project --with . python benchmarks/python_base64_batch.py
109
+ uv run --no-project --with . python benchmarks/python_murmur3.py
110
+ uv run --no-project --with . python benchmarks/python_murmur3.py --incremental
111
+ ```
112
+
113
+ ## Base64: Rust
114
+
115
+ | Alphabet | Input | Operation | hashcodecs | `base64` | `base64-turbo` |
116
+ | --- | --- | --- | ---: | ---: | ---: |
117
+ | Standard | 4 KiB | encode | **20.41 GiB/s** | 5.81 GiB/s | 18.29 GiB/s |
118
+ | | 4 KiB | decode | **27.11 GiB/s** | 4.33 GiB/s | 16.95 GiB/s |
119
+ | | 1 MiB | encode | **42.38 GiB/s** | 5.16 GiB/s | 19.60 GiB/s |
120
+ | | 1 MiB | decode | **31.07 GiB/s** | 4.10 GiB/s | 18.17 GiB/s |
121
+ | | 32 MiB | encode | **11.97 GiB/s** | 3.28 GiB/s | 10.94 GiB/s |
122
+ | | 32 MiB | decode | **11.51 GiB/s** | 3.35 GiB/s | 10.45 GiB/s |
123
+ | URL-safe | 4 KiB | encode | **20.46 GiB/s** | 5.81 GiB/s | 18.31 GiB/s |
124
+ | | 4 KiB | decode | **25.65 GiB/s** | 4.35 GiB/s | 16.96 GiB/s |
125
+ | | 1 MiB | encode | **42.56 GiB/s** | 5.18 GiB/s | 19.66 GiB/s |
126
+ | | 1 MiB | decode | **29.42 GiB/s** | 4.08 GiB/s | 18.16 GiB/s |
127
+ | | 32 MiB | encode | **11.86 GiB/s** | 3.29 GiB/s | 11.09 GiB/s |
128
+ | | 32 MiB | decode | **11.67 GiB/s** | 3.36 GiB/s | 10.62 GiB/s |
129
+
130
+ ## MurmurHash3: Rust
131
+
132
+ | Variant | Input | hashcodecs | `murmur3` | `murmurs` | `fastmurmur3` | `mm3h` |
133
+ | --- | --- | ---: | ---: | ---: | ---: | ---: |
134
+ | x86 32-bit | 4 KiB | **4.30 GiB/s** | 2.60 GiB/s | 4.09 GiB/s | n/a | **4.30 GiB/s** |
135
+ | | 1 MiB | **4.26 GiB/s** | 2.59 GiB/s | 4.04 GiB/s | n/a | 4.25 GiB/s |
136
+ | | 32 MiB | **4.21 GiB/s** | 2.54 GiB/s | 3.87 GiB/s | n/a | 4.03 GiB/s |
137
+ | x86 128-bit | 4 KiB | **9.54 GiB/s** | 4.97 GiB/s | 8.64 GiB/s | n/a | n/a |
138
+ | | 1 MiB | **9.84 GiB/s** | 5.06 GiB/s | 8.77 GiB/s | n/a | n/a |
139
+ | | 32 MiB | **9.63 GiB/s** | 4.84 GiB/s | 6.05 GiB/s | n/a | n/a |
140
+ | x64 128-bit | 4 KiB | **10.76 GiB/s** | 6.83 GiB/s | 9.45 GiB/s | 10.06 GiB/s | 9.54 GiB/s |
141
+ | | 1 MiB | **10.84 GiB/s** | 6.89 GiB/s | 9.51 GiB/s | 10.04 GiB/s | 9.51 GiB/s |
142
+ | | 32 MiB | **10.12 GiB/s** | 6.11 GiB/s | 6.64 GiB/s | 7.25 GiB/s | 6.70 GiB/s |
143
+
144
+ ## Base64: Python
145
+
146
+ Python decoding uses `validate=True`, and `hashcodecs` passes `bytes` directly into Rust without an input copy.
147
+
148
+ | Alphabet | Input | Operation | hashcodecs | CPython `base64` | `pybase64` |
149
+ | --- | --- | --- | ---: | ---: | ---: |
150
+ | Standard | 4 KiB | encode | 13.57 GiB/s | 0.48 GiB/s | **14.06 GiB/s** |
151
+ | | 4 KiB | decode | **16.70 GiB/s** | 1.13 GiB/s | 8.47 GiB/s |
152
+ | | 1 MiB | encode | **3.28 GiB/s** | 0.44 GiB/s | 3.02 GiB/s |
153
+ | | 1 MiB | decode | **4.02 GiB/s** | 0.95 GiB/s | 3.68 GiB/s |
154
+ | | 32 MiB | encode | **2.92 GiB/s** | 0.43 GiB/s | 2.84 GiB/s |
155
+ | | 32 MiB | decode | 3.40 GiB/s | 0.94 GiB/s | **3.69 GiB/s** |
156
+ | URL-safe | 4 KiB | encode | **12.63 GiB/s** | 0.41 GiB/s | 1.19 GiB/s |
157
+ | | 4 KiB | decode | **11.34 GiB/s** | 0.76 GiB/s | 1.56 GiB/s |
158
+ | | 1 MiB | encode | **3.24 GiB/s** | 0.36 GiB/s | 0.91 GiB/s |
159
+ | | 1 MiB | decode | **4.15 GiB/s** | 0.63 GiB/s | 1.45 GiB/s |
160
+ | | 32 MiB | encode | **3.03 GiB/s** | 0.36 GiB/s | 0.90 GiB/s |
161
+ | | 32 MiB | decode | **3.40 GiB/s** | 0.61 GiB/s | 1.48 GiB/s |
162
+
163
+ ## MurmurHash3: Python
164
+
165
+ | Variant | API | Input | hashcodecs | `mmh3` |
166
+ | --- | --- | --- | ---: | ---: |
167
+ | x86 32-bit | one-shot | 4 KiB | **3.79 GiB/s** | 3.71 GiB/s |
168
+ | | | 1 MiB | **3.98 GiB/s** | 3.83 GiB/s |
169
+ | | | 32 MiB | **3.97 GiB/s** | 3.66 GiB/s |
170
+ | | incremental | 4 KiB | **3.58 GiB/s** | **3.58 GiB/s** |
171
+ | | | 1 MiB | **3.96 GiB/s** | 3.81 GiB/s |
172
+ | | | 32 MiB | **3.92 GiB/s** | 3.74 GiB/s |
173
+ | x86 128-bit | one-shot | 4 KiB | 8.09 GiB/s | **8.21 GiB/s** |
174
+ | | | 1 MiB | **9.22 GiB/s** | 8.87 GiB/s |
175
+ | | | 32 MiB | **9.12 GiB/s** | 6.15 GiB/s |
176
+ | | incremental | 4 KiB | **7.02 GiB/s** | 0.77 GiB/s |
177
+ | | | 1 MiB | **9.17 GiB/s** | 0.80 GiB/s |
178
+ | | | 32 MiB | **9.01 GiB/s** | 0.79 GiB/s |
179
+ | x64 128-bit | one-shot | 4 KiB | 8.68 GiB/s | **9.46 GiB/s** |
180
+ | | | 1 MiB | 10.10 GiB/s | **10.24 GiB/s** |
181
+ | | | 32 MiB | **9.54 GiB/s** | 6.61 GiB/s |
182
+ | | incremental | 4 KiB | 7.70 GiB/s | **7.99 GiB/s** |
183
+ | | | 1 MiB | **10.01 GiB/s** | 9.28 GiB/s |
184
+ | | | 32 MiB | **9.41 GiB/s** | 7.34 GiB/s |
185
+
186
+ Reusable-buffer and mutable-input results are available in [BENCHMARK.md](https://github.com/kozistr/hashcodecs-rs/blob/main/BENCHMARK.md).
187
+
188
+ ## SIMD References
189
+
190
+ The SIMD implementation follows the approach described in [Faster Base64 Encoding and Decoding using AVX2 Instructions](https://arxiv.org/abs/1704.00605), with AVX-512 VBMI and AArch64 NEON backends selected automatically when available.
@@ -0,0 +1,170 @@
1
+ # hashcodecs
2
+
3
+ [![CI](https://img.shields.io/github/actions/workflow/status/kozistr/hashcodecs-rs/ci.yml?branch=main&style=for-the-badge&logo=github)](https://github.com/kozistr/hashcodecs-rs/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/hashcodecs?style=for-the-badge&logo=pypi)](https://pypi.org/project/hashcodecs/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/hashcodecs?style=for-the-badge&logo=python)](https://pypi.org/project/hashcodecs/)
6
+ [![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-brightgreen?style=for-the-badge)](https://github.com/kozistr/hashcodecs-rs#license)
7
+ [![Downloads](https://img.shields.io/pypi/dm/hashcodecs?style=for-the-badge&label=downloads)](https://pypi.org/project/hashcodecs/)
8
+
9
+ `hashcodecs` provides runtime-dispatched SIMD Base64 codecs and fast, reference-compatible MurmurHash3 functions for Rust and Python.
10
+
11
+ ## Design
12
+
13
+ - Runtime-dispatched SIMD Base64 with portable scalar fallbacks.
14
+ - Reference-compatible MurmurHash3 with SIMD acceleration.
15
+ - Rust and Python `*_into` APIs for caller-managed output buffers.
16
+ - A familiar Python Base64 API, including native batch encode and decode operations.
17
+
18
+ ## Install
19
+
20
+ ```
21
+ pip3 install hashcodecs
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Rust
27
+
28
+ ```rust
29
+ let encoded = hashcodecs::b64encode(b"hello");
30
+ assert_eq!(encoded, "aGVsbG8=");
31
+
32
+ let mut output = [0_u8; 8];
33
+ let written = hashcodecs::b64encode_into(b"hello", &mut output).unwrap();
34
+ assert_eq!(&output[..written], b"aGVsbG8=");
35
+ assert_eq!(hashcodecs::murmur3_x86_32(b"hello", 0), 0x248b_fa47);
36
+ ```
37
+
38
+ ### Python
39
+
40
+ ```python
41
+ import hashcodecs.base64 as base64
42
+ from hashcodecs import murmur3_32, murmur3_x64_128
43
+
44
+ assert base64.b64encode(b'hello') == b'aGVsbG8='
45
+ assert base64.b64decode(b'aGVsbG8=') == b'hello'
46
+ assert base64.b64encode_batch([b'hello', b'world']) == [b'aGVsbG8=', b'd29ybGQ=']
47
+ assert base64.b64decode_batch([b'aGVsbG8=', 'd29ybGQ=']) == [b'hello', b'world']
48
+ assert base64.b64encode(b'hello', padded=False) == b'aGVsbG8'
49
+ assert base64.b64decode(b'aGVsbG8', padded=False, canonical=True) == b'hello'
50
+ assert murmur3_32(b'hello') == 0x248BFA47
51
+
52
+ payload = b'hello'
53
+ encoded = bytearray(4 * ((len(payload) + 2) // 3))
54
+ encoded_len = base64.b64encode_into(payload, encoded)
55
+ decoded = bytearray(len(encoded))
56
+ decoded_len = base64.b64decode_into(encoded, decoded, validate=True)
57
+ assert encoded[:encoded_len] == b'aGVsbG8='
58
+ assert decoded[:decoded_len] == payload
59
+
60
+ hasher = murmur3_x64_128(seed=42)
61
+ hasher.update(b'hello')
62
+ assert hasher.hexdigest() == hasher.digest().hex()
63
+ ```
64
+
65
+ Build an installable wheel and source distribution with:
66
+
67
+ ```sh
68
+ uv build
69
+ ```
70
+
71
+ # Benchmark
72
+
73
+ Environment: Windows 10 x64 and Intel Core Ultra 7 265K.
74
+
75
+ Conditions: clean builds, one pinned logical CPU, single-threaded execution, 50 Rust samples, and 15 Python samples. Returned-output allocation is included except in the reusable-buffer table. Higher is better.
76
+
77
+ ## Run Locally
78
+
79
+ Comparison crates are development-only dependencies and are not included in consumer builds.
80
+
81
+ ```sh
82
+ cargo bench --bench base64
83
+ cargo bench --bench murmur3
84
+ uv sync --group benchmark --no-install-project
85
+ uv run --no-project --with . python benchmarks/python_base64.py
86
+ uv run --no-project --with . python benchmarks/python_base64.py --into
87
+ uv run --no-project --with . python benchmarks/python_base64.py --bytearray-input
88
+ uv run --no-project --with . python benchmarks/python_base64_batch.py
89
+ uv run --no-project --with . python benchmarks/python_murmur3.py
90
+ uv run --no-project --with . python benchmarks/python_murmur3.py --incremental
91
+ ```
92
+
93
+ ## Base64: Rust
94
+
95
+ | Alphabet | Input | Operation | hashcodecs | `base64` | `base64-turbo` |
96
+ | --- | --- | --- | ---: | ---: | ---: |
97
+ | Standard | 4 KiB | encode | **20.41 GiB/s** | 5.81 GiB/s | 18.29 GiB/s |
98
+ | | 4 KiB | decode | **27.11 GiB/s** | 4.33 GiB/s | 16.95 GiB/s |
99
+ | | 1 MiB | encode | **42.38 GiB/s** | 5.16 GiB/s | 19.60 GiB/s |
100
+ | | 1 MiB | decode | **31.07 GiB/s** | 4.10 GiB/s | 18.17 GiB/s |
101
+ | | 32 MiB | encode | **11.97 GiB/s** | 3.28 GiB/s | 10.94 GiB/s |
102
+ | | 32 MiB | decode | **11.51 GiB/s** | 3.35 GiB/s | 10.45 GiB/s |
103
+ | URL-safe | 4 KiB | encode | **20.46 GiB/s** | 5.81 GiB/s | 18.31 GiB/s |
104
+ | | 4 KiB | decode | **25.65 GiB/s** | 4.35 GiB/s | 16.96 GiB/s |
105
+ | | 1 MiB | encode | **42.56 GiB/s** | 5.18 GiB/s | 19.66 GiB/s |
106
+ | | 1 MiB | decode | **29.42 GiB/s** | 4.08 GiB/s | 18.16 GiB/s |
107
+ | | 32 MiB | encode | **11.86 GiB/s** | 3.29 GiB/s | 11.09 GiB/s |
108
+ | | 32 MiB | decode | **11.67 GiB/s** | 3.36 GiB/s | 10.62 GiB/s |
109
+
110
+ ## MurmurHash3: Rust
111
+
112
+ | Variant | Input | hashcodecs | `murmur3` | `murmurs` | `fastmurmur3` | `mm3h` |
113
+ | --- | --- | ---: | ---: | ---: | ---: | ---: |
114
+ | x86 32-bit | 4 KiB | **4.30 GiB/s** | 2.60 GiB/s | 4.09 GiB/s | n/a | **4.30 GiB/s** |
115
+ | | 1 MiB | **4.26 GiB/s** | 2.59 GiB/s | 4.04 GiB/s | n/a | 4.25 GiB/s |
116
+ | | 32 MiB | **4.21 GiB/s** | 2.54 GiB/s | 3.87 GiB/s | n/a | 4.03 GiB/s |
117
+ | x86 128-bit | 4 KiB | **9.54 GiB/s** | 4.97 GiB/s | 8.64 GiB/s | n/a | n/a |
118
+ | | 1 MiB | **9.84 GiB/s** | 5.06 GiB/s | 8.77 GiB/s | n/a | n/a |
119
+ | | 32 MiB | **9.63 GiB/s** | 4.84 GiB/s | 6.05 GiB/s | n/a | n/a |
120
+ | x64 128-bit | 4 KiB | **10.76 GiB/s** | 6.83 GiB/s | 9.45 GiB/s | 10.06 GiB/s | 9.54 GiB/s |
121
+ | | 1 MiB | **10.84 GiB/s** | 6.89 GiB/s | 9.51 GiB/s | 10.04 GiB/s | 9.51 GiB/s |
122
+ | | 32 MiB | **10.12 GiB/s** | 6.11 GiB/s | 6.64 GiB/s | 7.25 GiB/s | 6.70 GiB/s |
123
+
124
+ ## Base64: Python
125
+
126
+ Python decoding uses `validate=True`, and `hashcodecs` passes `bytes` directly into Rust without an input copy.
127
+
128
+ | Alphabet | Input | Operation | hashcodecs | CPython `base64` | `pybase64` |
129
+ | --- | --- | --- | ---: | ---: | ---: |
130
+ | Standard | 4 KiB | encode | 13.57 GiB/s | 0.48 GiB/s | **14.06 GiB/s** |
131
+ | | 4 KiB | decode | **16.70 GiB/s** | 1.13 GiB/s | 8.47 GiB/s |
132
+ | | 1 MiB | encode | **3.28 GiB/s** | 0.44 GiB/s | 3.02 GiB/s |
133
+ | | 1 MiB | decode | **4.02 GiB/s** | 0.95 GiB/s | 3.68 GiB/s |
134
+ | | 32 MiB | encode | **2.92 GiB/s** | 0.43 GiB/s | 2.84 GiB/s |
135
+ | | 32 MiB | decode | 3.40 GiB/s | 0.94 GiB/s | **3.69 GiB/s** |
136
+ | URL-safe | 4 KiB | encode | **12.63 GiB/s** | 0.41 GiB/s | 1.19 GiB/s |
137
+ | | 4 KiB | decode | **11.34 GiB/s** | 0.76 GiB/s | 1.56 GiB/s |
138
+ | | 1 MiB | encode | **3.24 GiB/s** | 0.36 GiB/s | 0.91 GiB/s |
139
+ | | 1 MiB | decode | **4.15 GiB/s** | 0.63 GiB/s | 1.45 GiB/s |
140
+ | | 32 MiB | encode | **3.03 GiB/s** | 0.36 GiB/s | 0.90 GiB/s |
141
+ | | 32 MiB | decode | **3.40 GiB/s** | 0.61 GiB/s | 1.48 GiB/s |
142
+
143
+ ## MurmurHash3: Python
144
+
145
+ | Variant | API | Input | hashcodecs | `mmh3` |
146
+ | --- | --- | --- | ---: | ---: |
147
+ | x86 32-bit | one-shot | 4 KiB | **3.79 GiB/s** | 3.71 GiB/s |
148
+ | | | 1 MiB | **3.98 GiB/s** | 3.83 GiB/s |
149
+ | | | 32 MiB | **3.97 GiB/s** | 3.66 GiB/s |
150
+ | | incremental | 4 KiB | **3.58 GiB/s** | **3.58 GiB/s** |
151
+ | | | 1 MiB | **3.96 GiB/s** | 3.81 GiB/s |
152
+ | | | 32 MiB | **3.92 GiB/s** | 3.74 GiB/s |
153
+ | x86 128-bit | one-shot | 4 KiB | 8.09 GiB/s | **8.21 GiB/s** |
154
+ | | | 1 MiB | **9.22 GiB/s** | 8.87 GiB/s |
155
+ | | | 32 MiB | **9.12 GiB/s** | 6.15 GiB/s |
156
+ | | incremental | 4 KiB | **7.02 GiB/s** | 0.77 GiB/s |
157
+ | | | 1 MiB | **9.17 GiB/s** | 0.80 GiB/s |
158
+ | | | 32 MiB | **9.01 GiB/s** | 0.79 GiB/s |
159
+ | x64 128-bit | one-shot | 4 KiB | 8.68 GiB/s | **9.46 GiB/s** |
160
+ | | | 1 MiB | 10.10 GiB/s | **10.24 GiB/s** |
161
+ | | | 32 MiB | **9.54 GiB/s** | 6.61 GiB/s |
162
+ | | incremental | 4 KiB | 7.70 GiB/s | **7.99 GiB/s** |
163
+ | | | 1 MiB | **10.01 GiB/s** | 9.28 GiB/s |
164
+ | | | 32 MiB | **9.41 GiB/s** | 7.34 GiB/s |
165
+
166
+ Reusable-buffer and mutable-input results are available in [BENCHMARK.md](https://github.com/kozistr/hashcodecs-rs/blob/main/BENCHMARK.md).
167
+
168
+ ## SIMD References
169
+
170
+ The SIMD implementation follows the approach described in [Faster Base64 Encoding and Decoding using AVX2 Instructions](https://arxiv.org/abs/1704.00605), with AVX-512 VBMI and AArch64 NEON backends selected automatically when available.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hashcodecs"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "SIMD-accelerated Base64 and MurmurHash3 codecs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -12,6 +12,12 @@ license = "MIT OR Apache-2.0"
12
12
  license-files = ["LICENSE", "LICENSE-MIT"]
13
13
  classifiers = [
14
14
  "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Programming Language :: Python :: 3.15",
15
21
  "Programming Language :: Rust",
16
22
  "Operating System :: OS Independent",
17
23
  "Topic :: Software Development :: Libraries",
@@ -33,6 +39,7 @@ packages = ["python/hashcodecs"]
33
39
 
34
40
  [tool.hatch.build.targets.sdist]
35
41
  include = [
42
+ "/BENCHMARK.md",
36
43
  "/Cargo.lock",
37
44
  "/Cargo.toml",
38
45
  "/build.rs",
@@ -2,16 +2,22 @@
2
2
 
3
3
  from .base64 import (
4
4
  b64decode,
5
+ b64decode_batch,
5
6
  b64decode_into,
6
7
  b64encode,
8
+ b64encode_batch,
7
9
  b64encode_into,
8
10
  standard_b64decode,
11
+ standard_b64decode_batch,
9
12
  standard_b64decode_into,
10
13
  standard_b64encode,
14
+ standard_b64encode_batch,
11
15
  standard_b64encode_into,
12
16
  urlsafe_b64decode,
17
+ urlsafe_b64decode_batch,
13
18
  urlsafe_b64decode_into,
14
19
  urlsafe_b64encode,
20
+ urlsafe_b64encode_batch,
15
21
  urlsafe_b64encode_into,
16
22
  )
17
23
  from .murmur3 import (
@@ -25,8 +31,10 @@ from .murmur3 import (
25
31
 
26
32
  __all__ = [
27
33
  'b64decode',
34
+ 'b64decode_batch',
28
35
  'b64decode_into',
29
36
  'b64encode',
37
+ 'b64encode_batch',
30
38
  'b64encode_into',
31
39
  'murmur3_32',
32
40
  'murmur3_x64_128',
@@ -35,11 +43,15 @@ __all__ = [
35
43
  'murmur3_x86_128',
36
44
  'murmur3_x86_128_digest',
37
45
  'standard_b64decode',
46
+ 'standard_b64decode_batch',
38
47
  'standard_b64decode_into',
39
48
  'standard_b64encode',
49
+ 'standard_b64encode_batch',
40
50
  'standard_b64encode_into',
41
51
  'urlsafe_b64decode',
52
+ 'urlsafe_b64decode_batch',
42
53
  'urlsafe_b64decode_into',
43
54
  'urlsafe_b64encode',
55
+ 'urlsafe_b64encode_batch',
44
56
  'urlsafe_b64encode_into',
45
57
  ]
@@ -1,17 +1,44 @@
1
1
  from _typeshed import ReadableBuffer
2
2
 
3
- def b64encode(s: ReadableBuffer, altchars: ReadableBuffer | None = None) -> bytes: ...
4
- def b64encode_into(s: ReadableBuffer, output: bytearray, altchars: ReadableBuffer | None = None) -> int: ...
3
+ def b64encode(
4
+ s: ReadableBuffer,
5
+ altchars: ReadableBuffer | None = None,
6
+ *,
7
+ padded: bool = True,
8
+ wrapcol: int = 0,
9
+ ) -> bytes: ...
10
+ def b64encode_batch(items: list[ReadableBuffer], altchars: ReadableBuffer | None = None) -> list[bytes]: ...
11
+ def b64encode_into(
12
+ s: ReadableBuffer,
13
+ output: bytearray,
14
+ altchars: ReadableBuffer | None = None,
15
+ *,
16
+ padded: bool = True,
17
+ wrapcol: int = 0,
18
+ ) -> int: ...
5
19
  def b64decode(
6
20
  s: str | ReadableBuffer,
7
21
  altchars: str | ReadableBuffer | None = None,
8
- validate: bool = False,
22
+ validate: bool = ...,
23
+ *,
24
+ padded: bool = True,
25
+ ignorechars: ReadableBuffer = ...,
26
+ canonical: bool = False,
9
27
  ) -> bytes: ...
28
+ def b64decode_batch(
29
+ items: list[str | ReadableBuffer],
30
+ altchars: str | ReadableBuffer | None = None,
31
+ validate: bool = False,
32
+ ) -> list[bytes]: ...
10
33
  def b64decode_into(
11
34
  s: str | ReadableBuffer,
12
35
  output: bytearray,
13
36
  altchars: str | ReadableBuffer | None = None,
14
- validate: bool = False,
37
+ validate: bool = ...,
38
+ *,
39
+ padded: bool = True,
40
+ ignorechars: ReadableBuffer = ...,
41
+ canonical: bool = False,
15
42
  ) -> int: ...
16
43
  def murmur3_32(s: ReadableBuffer, seed: int = 0) -> int: ...
17
44
  def murmur3_x86_128_digest(s: ReadableBuffer, seed: int = 0) -> bytes: ...
@@ -0,0 +1,89 @@
1
+ """A fast, API-compatible subset of Python's :mod:`base64` module."""
2
+
3
+ import sys
4
+
5
+ from ._hashcodecs import b64decode, b64decode_batch, b64decode_into, b64encode, b64encode_batch, b64encode_into
6
+
7
+ _URLSAFE_PADDED_DEFAULT = sys.version_info < (3, 15)
8
+
9
+
10
+ def standard_b64encode(s) -> bytes:
11
+ """Encode *s* with the standard Base64 alphabet."""
12
+ return b64encode(s)
13
+
14
+
15
+ def standard_b64encode_batch(items) -> list[bytes]:
16
+ """Encode a list of inputs with the standard Base64 alphabet."""
17
+ return b64encode_batch(items)
18
+
19
+
20
+ def standard_b64encode_into(s, output: bytearray) -> int:
21
+ """Encode *s* with the standard Base64 alphabet into *output*."""
22
+ return b64encode_into(s, output)
23
+
24
+
25
+ def standard_b64decode(s) -> bytes:
26
+ """Decode *s* with the standard Base64 alphabet."""
27
+ return b64decode(s)
28
+
29
+
30
+ def standard_b64decode_batch(items) -> list[bytes]:
31
+ """Decode a list of inputs with the standard Base64 alphabet."""
32
+ return b64decode_batch(items)
33
+
34
+
35
+ def standard_b64decode_into(s, output: bytearray) -> int:
36
+ """Decode standard Base64 *s* into *output*."""
37
+ return b64decode_into(s, output)
38
+
39
+
40
+ def urlsafe_b64encode(s, *, padded: bool = True) -> bytes:
41
+ """Encode *s* with the URL-safe Base64 alphabet."""
42
+ return b64encode(s, b'-_', padded=padded)
43
+
44
+
45
+ def urlsafe_b64encode_batch(items) -> list[bytes]:
46
+ """Encode a list of inputs with the URL-safe Base64 alphabet."""
47
+ return b64encode_batch(items, b'-_')
48
+
49
+
50
+ def urlsafe_b64encode_into(s, output: bytearray, *, padded: bool = True) -> int:
51
+ """Encode *s* with the URL-safe Base64 alphabet into *output*."""
52
+ return b64encode_into(s, output, b'-_', padded=padded)
53
+
54
+
55
+ def urlsafe_b64decode(s, *, padded: bool = _URLSAFE_PADDED_DEFAULT) -> bytes:
56
+ """Decode *s* with the URL-safe Base64 alphabet."""
57
+ return b64decode(s, b'-_', padded=padded)
58
+
59
+
60
+ def urlsafe_b64decode_batch(items) -> list[bytes]:
61
+ """Decode a list of inputs with the URL-safe Base64 alphabet."""
62
+ return b64decode_batch(items, b'-_')
63
+
64
+
65
+ def urlsafe_b64decode_into(s, output: bytearray, *, padded: bool = _URLSAFE_PADDED_DEFAULT) -> int:
66
+ """Decode URL-safe Base64 *s* into *output*."""
67
+ return b64decode_into(s, output, b'-_', padded=padded)
68
+
69
+
70
+ __all__ = [
71
+ 'b64decode',
72
+ 'b64decode_batch',
73
+ 'b64decode_into',
74
+ 'b64encode',
75
+ 'b64encode_batch',
76
+ 'b64encode_into',
77
+ 'standard_b64decode',
78
+ 'standard_b64decode_batch',
79
+ 'standard_b64decode_into',
80
+ 'standard_b64encode',
81
+ 'standard_b64encode_batch',
82
+ 'standard_b64encode_into',
83
+ 'urlsafe_b64decode',
84
+ 'urlsafe_b64decode_batch',
85
+ 'urlsafe_b64decode_into',
86
+ 'urlsafe_b64encode',
87
+ 'urlsafe_b64encode_batch',
88
+ 'urlsafe_b64encode_into',
89
+ ]