Performance Testing

Measured overhead on systems with hardware shadow stacks

The numbers below are the performance overhead of running protected builds on systems with hardware shadow stacks. Each workload was run 20 times, interleaving the protected and baseline builds so that any drift in machine state over the session lands on both equally, and the figures below are the medians of those runs. Both builds were produced with the same compiler and flags and measured on the same machine in the same session.

Overhead by workload Every workload plotted as percent slower than an unprotected baseline. All eleven land within four percent, and the largest are the shortest-running workloads. Fil-C, which enforces full memory safety, is shown below on a broken axis at roughly four hundred percent, about a hundred times further out. WORKLOAD % SLOWER THAN BASELINE 0% 1% 2% 3% 4% FFmpeg MPEG-4 encode +0.98% FFmpeg MPEG-4 decode +2.09% FFmpeg MJPEG encode +1.34% FFmpeg AAC encode +4.01% FFmpeg AAC decode +3.68% OpenSSL AES-128-CBC +0.15% OpenSSL AES-256-GCM −0.04% OpenSSL SHA-256 +2.14% OpenSSL RSA-2048 +1.02% OpenSSL ECDSA-P256 +0.35% SQLite speedtest1 +0.63% Fil-C (full memory safety) ~400% off this scale by about 100×
% slower is measured against an unmodified baseline built with the same compiler and flags. The throughput rows are sign-inverted here so that every bar is directly comparable; the table below reports each result against its own raw metric. Fil-C is shown for scale on a broken axis and is not our measurement, it is the figure its own documentation reports, on its own workloads, and it buys full spatial and temporal memory safety.
Program / workload Baseline median Protected median Change
FFmpeg MPEG-4 encode1.518 s1.533 s+0.98%
FFmpeg MPEG-4 decode1.208 s1.234 s+2.09%
FFmpeg MJPEG encode4.515 s4.575 s+1.34%
FFmpeg AAC encode0.1289 s0.1341 s+4.01%
FFmpeg AAC decode0.0167 s0.0173 s+3.68%
OpenSSL AES-128-CBC1.953 GB/s1.950 GB/s−0.15%
OpenSSL AES-256-GCM13.047 GB/s13.052 GB/s+0.04%
OpenSSL SHA-2562.441 GB/s2.389 GB/s−2.14%
OpenSSL RSA-20485,509 ops/s5,453 ops/s−1.02%
OpenSSL ECDSA-P25623,405 ops/s23,324 ops/s−0.35%
SQLite speedtest14.004 s4.029 s+0.63%

Change is reported against the raw metric. For the time-based rows (seconds) lower is better, so a positive change is slower. For the throughput rows (GB/s, ops/s) higher is better, so a negative change is slower.

Reading the numbers

Every workload lands within a few percent of baseline. The largest movements are on the shortest runs. AAC encode and decode complete in well under a fifth of a second, where fixed startup costs make up a visible share of the total, so their percentages overstate the steady-state cost. The long-running workloads, which are the ones that matter for throughput, stay near or under 1%.

The cryptographic rows are a pathological case to measure, and they move in both directions. Code of this kind spends nearly all its time inside tight, hand-tuned inner loops that make almost no indirect calls, so there is very little for the protection to act on and correspondingly little real cost to find. What is left is a measurement problem rather than an overhead: at this magnitude the gap between two builds is dominated by incidental effects such as code and data layout, which shift whenever a binary is rebuilt at all. That is why one protected build comes out marginally faster than baseline. Neither direction is signal, and the honest reading is that the protection is not measurably visible on this kind of code.