Benchmarks

This is a portable pure-OCaml implementation. The numbers below are a baseline to reason about and to reproduce.

dune exec --profile release bench/bench_ascon.exe

Measure on your own machine. These figures come from one run on one laptop. Cryptographic throughput is sensitive to the compiler, the runtime, and the profile; comparisons are only meaningful between results measured on the same hardware.

Start with the permutation

Everything here is the same 320-bit permutation applied over and over, so one number sets the ceiling for all four constructions:

Ascon-p[12], the shared 12-round permutation
Time per permutation447 ns
Permutations per second2.24 million
Time per round37 ns

The rest follows from the standard's parameters. Hashing absorbs 8 bytes per p[12], which is 1.5 rounds per byte; AEAD processes 16 bytes per p[8], which is 0.5. At 37 ns per round that predicts roughly 18 MB/s for the hash family and 54 MB/s for AEAD — and that is very close to what the full constructions actually measure. There is almost nothing in the hot loop but the permutation.

Throughput and allocation

Apple ARM64, macOS 26.6, OCaml 5.5.0, dune release profile
Operation 1 KiB 1 MiB Allocated words/op at 1 KiB
AEAD128 encrypt47.4 MB/s51.0 MB/s15,898
AEAD128 decrypt48.4 MB/s42.4 MB/s15,898
Hash25618.8 MB/s19.9 MB/s44,254
XOF128, 32-byte output19.2 MB/s18.6 MB/s44,254
CXOF128, 32-byte output17.2 MB/s19.2 MB/s45,295

AEAD runs about two and a half times faster per byte than the hash family. That gap is the standard's, not the implementation's: the parameters alone predict 3×, and what is missing is the two extra p[12] permutations and the associated-data blocks that every AEAD message pays regardless of its length.

Where the allocation goes

The allocation column looks alarming until you divide it by the number of permutation rounds each operation performs. A 1 KiB Hash256 digest runs 134 permutations, or 1,608 rounds; a 1 KiB AEAD encryption runs 552 rounds. Both land at roughly 28 words per round.

That is not the persistent-context API being wasteful. Absorbing copies the five-word state once per call, not once per block — a few dozen words. The rest is OCaml boxing Int64 intermediates inside the round function, which computes ten or so of them per round and stores five back into the state record. It is short-lived minor-heap garbage that the collector handles cheaply, but on an allocation-averse target it is the number to know.

Permutation rounds per operation
Operation on 1 KiB Rounds Words/round
Hash256 digest 134 × p[12] = 1,608 27.5
AEAD128 encrypt 66 × p[8] + 2 × p[12] = 552 28.8

The Hash256 count is one p[12] to initialize, 128 to absorb the message, one for the padded final block, and four to squeeze 32 bytes. The AEAD count is 64 payload blocks plus two associated-data blocks at p[8], with p[12] at initialization and finalization.

The full matrix

The committed benchmark reports every construction at 0, 8, 16, 32, 64, 256, 4,096, 16,384, and 1,048,576 bytes as well as the two sizes above, printing nanoseconds per operation, throughput, and allocated words for each. The small sizes are the interesting ones for a lightweight primitive: at 8 or 16 bytes the fixed cost of initialization and finalization dominates completely, and throughput in MB/s stops being a useful way to read the result. Compare ns/op there instead.

What these numbers are not

The benchmark source is bench/bench_ascon.ml. It compacts the heap and samples Gc.quick_stat around each measurement, and scales its iteration count so that small sizes still get a meaningful sample.