The OpenSSL in the macOS wheels is built with no -O flag, so clang compiles it at -O0. OpenSSL records its own compiler line, and in the arm64 wheel of 50.0.1 it reads:
compiler: cc -fPIC -arch arm64 -mmacosx-version-min=11.0 -DL_ENDIAN -DOPENSSL_PIC -D_REENTRANT -DNDEBUG
The manylinux x86-64 wheel of the same release has -O3 there.
Cause. The "Build OpenSSL" step in pyca/infra's .github/workflows/build-macos-openssl.yml sets CFLAGS in its environment (-mmacosx-version-min=11.0 on arm64, -mmacosx-version-min=10.12 -march=core2 on x86-64). OpenSSL's Configure treats an environment CFLAGS as a replacement for the target's defaults, not an addition, so the -O3 -Wall that darwin64-arm64-cc normally uses is dropped. With OpenSSL 4.0.2:
$ CFLAGS="-mmacosx-version-min=11.0" perl Configure darwin64-arm64-cc no-shared
$ grep '^CFLAGS=' Makefile
CFLAGS=-mmacosx-version-min=11.0
$ perl Configure darwin64-arm64-cc no-shared -mmacosx-version-min=11.0
$ grep '^CFLAGS=' Makefile
CFLAGS=-O3 -Wall -mmacosx-version-min=11.0
Which wheels. Every macOS wheel I checked: arm64 3.4.8, 35.0.0, 36.0.0 and 50.0.1; x86-64 3.4; and both slices of the 42.0.0 universal2 wheel.
Effect. Whatever OpenSSL implements in C runs about ten times slower. Whatever has an assembly path barely changes, which is probably why this has gone unnoticed: SHA-2, SHA-3 and P-256 look normal. On arm64 every post-quantum algorithm is C. Apple M1 Pro, cryptography 50.0.1 over OpenSSL 4.0.2, the wheel against the same two releases built from source with -O3, median of 1,000 calls in ms:
| Operation |
OpenSSL path |
-O3 |
wheel |
ratio |
| SHA3-256, 64 KiB |
assembly |
0.0722 |
0.0725 |
1.0× |
| ECDSA P-256 sign |
assembly |
0.0221 |
0.0262 |
1.2× |
| Ed25519 sign |
C + assembly |
0.0295 |
0.0923 |
3.1× |
| ML-DSA-65 sign |
C |
0.4871 |
4.4833 |
9.2× |
| ML-DSA-65 verify |
C |
0.1075 |
0.8487 |
7.9× |
| ML-KEM-768 encapsulate |
C |
0.0130 |
0.1323 |
10.2× |
| ML-KEM-768 decapsulate |
C |
0.0181 |
0.1995 |
11.0× |
To check an installation:
python -c "import cryptography,glob,os;so=glob.glob(os.path.join(os.path.dirname(cryptography.__file__),'hazmat','bindings','_rust*.so'))[0];b=open(so,'rb').read();i=b.find(b'compiler: ');print(b[i:b.index(b'\0',i)].decode())"
I found this while benchmarking ML-DSA for a measurement study. A fix is in pyca/infra#796.
The OpenSSL in the macOS wheels is built with no
-Oflag, so clang compiles it at-O0. OpenSSL records its own compiler line, and in the arm64 wheel of 50.0.1 it reads:The manylinux x86-64 wheel of the same release has
-O3there.Cause. The "Build OpenSSL" step in pyca/infra's
.github/workflows/build-macos-openssl.ymlsetsCFLAGSin its environment (-mmacosx-version-min=11.0on arm64,-mmacosx-version-min=10.12 -march=core2on x86-64). OpenSSL'sConfiguretreats an environmentCFLAGSas a replacement for the target's defaults, not an addition, so the-O3 -Wallthatdarwin64-arm64-ccnormally uses is dropped. With OpenSSL 4.0.2:Which wheels. Every macOS wheel I checked: arm64 3.4.8, 35.0.0, 36.0.0 and 50.0.1; x86-64 3.4; and both slices of the 42.0.0 universal2 wheel.
Effect. Whatever OpenSSL implements in C runs about ten times slower. Whatever has an assembly path barely changes, which is probably why this has gone unnoticed: SHA-2, SHA-3 and P-256 look normal. On arm64 every post-quantum algorithm is C. Apple M1 Pro, cryptography 50.0.1 over OpenSSL 4.0.2, the wheel against the same two releases built from source with
-O3, median of 1,000 calls in ms:-O3To check an installation:
I found this while benchmarking ML-DSA for a measurement study. A fix is in pyca/infra#796.