hashlib blake2b reached 615 MB/s while sha256 managed 145 MB/s
In this article
Passing 64MB of random data through hashlib made blake2b the fastest at 615 MB/s, while sha256 sank to the bottom at 145 MB/s. This was quite different from my initial expectation.
The ordering was the reverse of what I expected, and the table looked uncomfortable. I did not notice until I saw the measured values, and I had assumed sha256 would be near the top. I had the vague expectation that sha256 would benefit nicely from hardware instructions on ARM64, but it was below md5 at 315 MB/s, sha1 at 388 MB/s, sha512 at 289 MB/s, and sha3_256 at 351 MB/s. One possible reason blake2b was fast is that its 64-bit-oriented processing benefited directly. Even so, I did not expect sha256 to be this slow.
The Python version was 3.12.10, and the OpenSSL behind hashlib was OpenSSL 3.0.16 11 Feb 2025. Seeing that combination made me want to investigate the OpenSSL build or runtime feature detection rather than Python's code. However, I did not disassemble anything or inspect the OpenSSL configuration, so it is too early to choose a cause. I organized this on June 10, 2026; the original log timestamp remains 2026-06-10T20:17:43.225432+09:00.
Feed the same 64MB through repeatedly
I created one 64MB sequence of random bytes and passed it to md5, sha1, sha256, sha512, blake2b, blake2s, and sha3_256 in that order. I measured each algorithm five times and calculated MB/s from the median.
import hashlib
import os
import time
data = os.urandom(64 * 1024 * 1024)
algos = ["md5", "sha1", "sha256", "sha512", "blake2b", "blake2s", "sha3_256"]
for name in algos:
h = getattr(hashlib, name)
runs = []
for _ in range(5):
t0 = time.perf_counter()
h(data).digest()
runs.append((time.perf_counter() - t0) * 1000)
print(name, runs)
There was no file I/O. Reading from disk would dilute the differences between the hash functions, so I wanted to measure only the time to call digest() on the same in-memory data. At 64MB, the input was not so short that the result was just timer noise, and not so long that waiting became unreasonable. The choice of size was mostly habit; the rationale came afterward.
I chose hashlib because it is what I use most directly in everyday scripts. For duplicate detection or checking content, I often want to stay within the Python standard library rather than invoke an external command, and getting as far as digest() in the same process is convenient. This was a basic check of which standard option produces the least waiting.
The position of sha256 stands out in the table
Here are all five measured values. The median can make the run look stable, but sha256 varied substantially; smoothing that away would hide an important detail.
| Algorithm | runs_ms (5 runs) |
median_ms |
MB/s |
|---|---|---|---|
| md5 | 209.51 / 200.11 / 199.68 / 203.04 / 216.26 | 203.04ms | 315 MB/s |
| sha1 | 165.00 / 174.22 / 160.07 / 160.25 / 176.43 | 165.00ms | 388 MB/s |
| sha256 | 291.14 / 441.03 / 479.89 / 283.45 / 474.01 | 441.03ms | 145 MB/s |
| sha512 | 221.59 / 228.63 / 251.96 / 190.54 / 213.93 | 221.59ms | 289 MB/s |
| blake2b | 95.53 / 104.33 / 102.56 / 104.06 / 121.91 | 104.06ms | 615 MB/s |
| blake2s | 161.11 / 152.84 / 165.72 / 176.99 / 177.32 | 165.72ms | 386 MB/s |
| sha3_256 | 162.95 / 182.35 / 191.13 / 159.40 / 199.96 | 182.35ms | 351 MB/s |
blake2b at 615 MB/s is well above the rest of the table. sha1 is next at 388 MB/s, followed closely by blake2s at 386 MB/s, so blake2b appears to be in a different tier. Still, there is not enough evidence here to jump to a microarchitecture explanation. I measured the result of calling hashlib from Python, not the performance of individual instructions.
The 145 MB/s for sha256 was the part that bothered me. sha512 reached 289 MB/s, so despite the impression that its name sounds heavier, sha512 appears almost twice as fast. The five sha256 runs were 291.14ms, 441.03ms, 479.89ms, 283.45ms, and 474.01ms; the spread between the fast and slow runs was also large. That variation is easy to miss if you look only at the median.
Suspect OpenSSL, but do not decide yet
ARM64 has crypto extensions for SHA-256. I therefore expected a native Python call to hashlib.sha256() to become naturally fast. It did not, so my first interpretation failed.
OpenSSL is the first place I would investigate rather than Python. Much of hashlib uses OpenSSL's implementation, and this run reports OpenSSL 3.0.16 11 Feb 2025. It does not look like a case of falling back to an old OpenSSL, but whether the build enables ARM crypto extensions and whether the runtime selects the relevant path are separate questions. Not checking those was a weakness in this experiment.
It would be easy to write that “the CPU is bad,” but that would probably be too careless. The OpenSSL path, Python's call layer, and scheduling during the measurement all remain possible causes. Why did only sha256 vary this much? Answering that requires OpenSSL feature information or a repeat measurement with a different build.
Do not mix this with the x64-emulation article
This site has an article where x64 emulation reversed the ordering for SHA-256. This is not that comparison. It is about what happened when I switched algorithms within native ARM64 Python 3.12.10 and hashlib.
I therefore chose not to discuss whether x64 is faster or slower. The findings here are limited to the fact that algorithm choice makes a difference within the same hashlib, and that sha256 was not where I expected it to be. Mixing in unmeasured areas would make the story bigger than the data. The combination of ARM64, SHA-256, OpenSSL, and crypto extensions makes that temptation strong.
For practical use, I decided to try blake2b for duplicate detection where cryptographic strength is not the first priority. At 615 MB/s, it has less waiting under these conditions, and it is available as Python's standard hashlib.blake2b() without adding a dependency.
I will keep sha256 where compatibility with an external system is required. Existing checksums, APIs, and distribution verification cannot be changed to blake2b just for my convenience. Even at 145 MB/s, 64MB takes 441.03ms, which is not a serious problem for a one-off operation.
Next, I would inspect the OpenSSL build settings and CPU feature detection. If possible, I would repeat the same 64MB test with another OpenSSL build or Python distribution. The present log supports “sha256 was the slowest,” but not “why it was the slowest.”