hashcodecs 0.2.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 (41) hide show
  1. hashcodecs-0.4.0/BENCHMARK.md +71 -0
  2. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/Cargo.lock +1 -1
  3. {hashcodecs-0.2.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.2.0 → hashcodecs-0.4.0}/hatch_build.py +19 -19
  7. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/pyproject.toml +22 -1
  8. hashcodecs-0.4.0/python/hashcodecs/__init__.py +57 -0
  9. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/python/hashcodecs/_hashcodecs.pyi +36 -2
  10. hashcodecs-0.4.0/python/hashcodecs/base64.py +89 -0
  11. hashcodecs-0.4.0/python/hashcodecs/base64.pyi +54 -0
  12. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/python/hashcodecs/murmur3.py +6 -6
  13. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/base64/aarch64.rs +115 -60
  14. hashcodecs-0.4.0/src/base64/x86.rs +822 -0
  15. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/base64/x86_avx512.rs +60 -37
  16. hashcodecs-0.4.0/src/python/base64/encode.rs +163 -0
  17. hashcodecs-0.4.0/src/python/base64.rs +792 -0
  18. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/python/buffer.rs +37 -12
  19. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/python/murmur3.rs +28 -49
  20. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/python.rs +7 -1
  21. hashcodecs-0.2.0/PKG-INFO +0 -179
  22. hashcodecs-0.2.0/README.md +0 -165
  23. hashcodecs-0.2.0/python/hashcodecs/__init__.py +0 -26
  24. hashcodecs-0.2.0/python/hashcodecs/base64.py +0 -33
  25. hashcodecs-0.2.0/python/hashcodecs/base64.pyi +0 -12
  26. hashcodecs-0.2.0/src/base64/x86.rs +0 -643
  27. hashcodecs-0.2.0/src/python/base64.rs +0 -217
  28. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/.gitignore +0 -0
  29. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/LICENSE +0 -0
  30. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/LICENSE-MIT +0 -0
  31. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/benches/base64.rs +0 -0
  32. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/benches/murmur3.rs +0 -0
  33. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/benches/support/mod.rs +0 -0
  34. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/build.rs +0 -0
  35. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/python/hashcodecs/py.typed +0 -0
  36. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/base64/dispatch.rs +0 -0
  37. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/base64.rs +0 -0
  38. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/lib.rs +0 -0
  39. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/murmur3/dispatch.rs +0 -0
  40. {hashcodecs-0.2.0 → hashcodecs-0.4.0}/src/murmur3/x86.rs +0 -0
  41. {hashcodecs-0.2.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.2.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.2.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.
@@ -15,42 +15,42 @@ class CustomBuildHook(BuildHookInterface[Any]):
15
15
  """Build the portable ABI3 extension and give the wheel its native tag."""
16
16
 
17
17
  def initialize(self, version: str, build_data: dict[str, Any]) -> None:
18
- if self.target_name != "wheel":
18
+ if self.target_name != 'wheel':
19
19
  return
20
20
 
21
21
  root = Path(self.root)
22
22
  subprocess.run(
23
23
  [
24
- "cargo",
25
- "build",
26
- "--manifest-path",
27
- str(root / "Cargo.toml"),
28
- "--release",
29
- "--features",
30
- "extension-module",
24
+ 'cargo',
25
+ 'build',
26
+ '--manifest-path',
27
+ str(root / 'Cargo.toml'),
28
+ '--release',
29
+ '--features',
30
+ 'extension-module',
31
31
  ],
32
32
  check=True,
33
33
  cwd=root,
34
34
  )
35
35
 
36
- extension = root / "target" / "release" / self._library_name()
37
- build_data["force_include"] = {str(extension): f"hashcodecs/_hashcodecs{self._extension_suffix()}"}
38
- build_data["pure_python"] = False
39
- build_data["tag"] = self._wheel_tag()
36
+ extension = root / 'target' / 'release' / self._library_name()
37
+ build_data['force_include'] = {str(extension): f'hashcodecs/_hashcodecs{self._extension_suffix()}'}
38
+ build_data['pure_python'] = False
39
+ build_data['tag'] = self._wheel_tag()
40
40
 
41
41
  @staticmethod
42
42
  def _library_name() -> str:
43
43
  system = platform.system()
44
- if system == "Windows":
45
- return "hashcodecs.dll"
46
- if system == "Darwin":
47
- return "libhashcodecs.dylib"
48
- return "libhashcodecs.so"
44
+ if system == 'Windows':
45
+ return 'hashcodecs.dll'
46
+ if system == 'Darwin':
47
+ return 'libhashcodecs.dylib'
48
+ return 'libhashcodecs.so'
49
49
 
50
50
  @staticmethod
51
51
  def _extension_suffix() -> str:
52
- return ".pyd" if platform.system() == "Windows" else ".so"
52
+ return '.pyd' if platform.system() == 'Windows' else '.so'
53
53
 
54
54
  @staticmethod
55
55
  def _wheel_tag() -> str:
56
- return f"cp310-abi3-{next(sys_tags()).platform}"
56
+ return f'cp310-abi3-{next(sys_tags()).platform}'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hashcodecs"
7
- version = "0.2.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",
@@ -66,6 +73,7 @@ line-length = 119
66
73
  target-version = "py312"
67
74
 
68
75
  [tool.ruff.lint]
76
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
69
77
  select = [
70
78
  "E",
71
79
  "F",
@@ -79,11 +87,24 @@ select = [
79
87
  "PTH",
80
88
  "PT",
81
89
  "PYI",
90
+ "Q",
82
91
  "RET",
83
92
  "RUF",
84
93
  "SIM",
85
94
  ]
86
95
 
96
+ [tool.ruff.lint.flake8-quotes]
97
+ docstring-quotes = "double"
98
+ inline-quotes = "single"
99
+
100
+ [tool.ruff.lint.isort]
101
+ combine-as-imports = false
102
+ detect-same-package = true
103
+ force-sort-within-sections = false
104
+
105
+ [tool.ruff.format]
106
+ quote-style = "single"
107
+
87
108
  [tool.coverage.run]
88
109
  branch = true
89
110
  source = ["hashcodecs"]
@@ -0,0 +1,57 @@
1
+ """SIMD-accelerated Base64 and MurmurHash3 functions."""
2
+
3
+ from .base64 import (
4
+ b64decode,
5
+ b64decode_batch,
6
+ b64decode_into,
7
+ b64encode,
8
+ b64encode_batch,
9
+ b64encode_into,
10
+ standard_b64decode,
11
+ standard_b64decode_batch,
12
+ standard_b64decode_into,
13
+ standard_b64encode,
14
+ standard_b64encode_batch,
15
+ standard_b64encode_into,
16
+ urlsafe_b64decode,
17
+ urlsafe_b64decode_batch,
18
+ urlsafe_b64decode_into,
19
+ urlsafe_b64encode,
20
+ urlsafe_b64encode_batch,
21
+ urlsafe_b64encode_into,
22
+ )
23
+ from .murmur3 import (
24
+ murmur3_32,
25
+ murmur3_x64_128,
26
+ murmur3_x64_128_digest,
27
+ murmur3_x86_32,
28
+ murmur3_x86_128,
29
+ murmur3_x86_128_digest,
30
+ )
31
+
32
+ __all__ = [
33
+ 'b64decode',
34
+ 'b64decode_batch',
35
+ 'b64decode_into',
36
+ 'b64encode',
37
+ 'b64encode_batch',
38
+ 'b64encode_into',
39
+ 'murmur3_32',
40
+ 'murmur3_x64_128',
41
+ 'murmur3_x64_128_digest',
42
+ 'murmur3_x86_32',
43
+ 'murmur3_x86_128',
44
+ 'murmur3_x86_128_digest',
45
+ 'standard_b64decode',
46
+ 'standard_b64decode_batch',
47
+ 'standard_b64decode_into',
48
+ 'standard_b64encode',
49
+ 'standard_b64encode_batch',
50
+ 'standard_b64encode_into',
51
+ 'urlsafe_b64decode',
52
+ 'urlsafe_b64decode_batch',
53
+ 'urlsafe_b64decode_into',
54
+ 'urlsafe_b64encode',
55
+ 'urlsafe_b64encode_batch',
56
+ 'urlsafe_b64encode_into',
57
+ ]