Host
Intel Core Ultra 9 285K · 24 cores
Platform
linux/amd64
Go
go1.27.0
CPython
python:3.14-slim
PyPy
pypy:3.11-slim
Runs / combo
7 + 2 warmup

Per-line SHA-256 (FFI overhead)

Compute a 32-byte content digest of Frankenstein (Project Gutenberg #84, ~7,700 lines) by hashing each line with the runner's bundled SHA-256 primitive and XOR-folding into a running accumulator. Measures interpreter throughput + per-call native FFI overhead in equal measure.

Compile time · median (cold)

median of 7 runs

Native Gocompiled
120 ms110×
Pipitbytecode VM
1.09 msbaseline
CPython 3.14bytecode VM
310 µs0.28×
PyPy 7.3tracing JIT
247 µs0.23×
Ttengobytecode VM
189 µs0.17×
Sscriggobytecode VM
421 µs0.39×
Mmvmbytecode VM
315 µs0.29×
YyaegiAST walker
262 µs0.24×

Full statistics

RunnerNCompileRuntimeP95StddevRSSvs pipitStatus
Native Gocompiled7120 ms2.01 ms2.01 ms1.51 µs1.62 GiB110×OK
Pipitbytecode VM71.09 ms31.7 ms49.6 ms7.09 ms592 MiB1.00×OK
CPython 3.14bytecode VM7310 µs47.9 ms48.3 ms489 µsn/a0.28×OK
PyPy 7.3tracing JIT7247 µs22.4 ms23.8 ms680 µsn/a0.23×OK
tengobytecode VM7189 µs79.9 ms94.8 ms6.32 ms1.69 GiB0.17×OK
scriggobytecode VM7421 µs81.6 ms100 ms11.4 ms1.69 GiB0.39×OK
mvmbytecode VM7315 µs83.8 ms84.6 ms497 µs1.69 GiB0.29×OK
yaegiAST walker7262 µs55.7 ms71.4 ms7.08 ms1.69 GiB0.24×OK
Workload & symmetry rules

Workload

Walk Frankenstein's text (~441 KiB, ~7,700 lines) byte-by-byte to find newline boundaries. For each line, call the runner's bundled SHA-256 primitive once. XOR-fold every 32-byte digest into a running accumulator. Emit the accumulator as 64 lowercase hex characters.

Symmetry rules

  • One SHA-256 call per line (~7,700 native FFI dispatches per inner iteration). Whole-buffer hashing (sha256.Sum256(corpus) / hashlib.sha256(corpus).hexdigest()) is banned because it would do the work in a single native call, hiding the per-call signal.
  • Line scan and XOR fold run in interpreted code on every runner. No strings.Split, no str.splitlines.
  • Hand-rolled hex emission (no encoding/hex, no binascii.hexlify) so the trailing format step also runs in the interpreter.
  • Tengo's stdlib does not bundle SHA-256, so the harness registers a sha256_sum builtin that wraps Go's crypto/sha256.Sum256. Same shape of "register a host function as a native callout" that every other runner uses, just via Tengo's UserFunction surface.

Why this benchmark exists

This is the only benchmark that exercises the script-orchestrates-compiled-primitive pattern, which is how most production embedded interpreters are actually used (NumPy from Python, engine bindings from Lua, host functions from Tengo). Per-call FFI dispatch overhead is what discriminates runners: with ~7,700 calls per inner iteration, a 1µs-per-call difference adds up to ~8 ms of wall time, visible against a ~100 ms baseline. Every runner uses the same SHA-256 algorithm routed through the same compression primitive (OpenSSL on Python, Go's crypto/sha256 with ASM where available on Go family), so the per-block hash cost is comparable across the board.

Source code