# Constantine vs nimcrypto benchmark
benchmarking has been done after importing both into `nimbus-eth1` project
Please refer the following benchmarking code
```nim
# Nimbus
# Copyright (c) 2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
std/[monotimes, times, math, strformat],
eth/common,
nimcrypto/keccak,
constantine/hashes
type
KECCACK256* = h_keccak.KeccakContext[256, 0x01]
func keccak_nimcrypto*(input: openArray[byte]): Hash32 =
var ctx: keccak.keccak256
ctx.update(input)
ctx.finish().to(Hash32)
func keccak_nimcrypto*(input: openArray[char]): Hash32 =
keccak_nimcrypto(input.toOpenArrayByte(0, input.high))
func keccak_constantine*(input: openArray[byte]): Hash32 =
var
ctx: KECCACK256
buff: array[32, byte]
ctx.update(input)
ctx.finish(buff)
Hash32(buff)
func keccak_constantine*(input: openArray[char]): Hash32 =
keccak_constantine(input.toOpenArrayByte(0, input.high))
const
INPUT_32_BYTE = "1234567890abcdef1234567890abcdef"
INPUT_64_BYTE = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
INPUT_128_BYTE = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
INPUT_256_BYTE = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
# Utility: turn string into a seq[byte] once, to avoid per-iter conversion costs.
proc toBytesOnce(s: string): seq[byte] =
result = newSeq[byte](s.len)
if s.len > 0:
# Copy raw bytes of the string into result
when compiles(copyMem):
copyMem(addr result[0], unsafeAddr s[0], s.len)
else:
for i, c in s: result[i] = byte(c)
# Benchmark runner
proc bench(name: string,
hashFn: proc(data: openArray[byte]): Hash32 {.noSideEffect.},
data: openArray[byte],
iters: int) =
# Warm-up
var acc: uint32 = 0
for _ in 0 ..< min(iters, 10_000):
let h = hashFn(data)
# consume 4 bytes to avoid DCE; Hash32 is 32 bytes
acc = acc xor cast[ptr uint32](unsafeAddr h)[]
# Timed run
let start = getMonoTime()
for _ in 0 ..< iters:
let h = hashFn(data)
acc = acc xor cast[ptr uint32](unsafeAddr h)[]
let dur = getMonoTime() - start
# Metrics
let nsTotal = dur.inNanoseconds.float
let nsPerOp = nsTotal / iters.float
let bytesPerOp = data.len.float
let mbps = (bytesPerOp * iters.float) / (dur.inSeconds.float * 1024.0 * 1024.0)
echo fmt"{name:>30} | size={data.len:>4} iters={iters:>9} ns/op={nsPerOp:>10.1f} MB/s={mbps:>8.1f} (acc={acc})"
# Choose iteration counts to keep each test ~200–400ms on a typical dev box.
# Scale inversely with input length so total hashed bytes per test are similar.
proc chooseIters(size: int): int =
let targetBytes = 64 * 1024 * 1024 # ~64 MiB per test
max(1, targetBytes div max(1, size))
type DataKind = enum
dkZeros, dkPattern, dkPRG
proc genData(kind: DataKind; size: int; seed: string = "eth-blocks"): seq[byte] =
## Deterministic payload generator for benchmarking.
result = newSeq[byte](size)
case kind
of dkZeros:
# already zero-initialized
discard
of dkPattern:
# Convert pattern string to an addressable array
const patStr = "1234567890abcdef"
var pat: array[patStr.len, byte]
for i, c in patStr:
pat[i] = byte(c)
var i = 0
while i < size:
let chunk = min(pat.len, size - i)
when compiles(copyMem):
copyMem(addr result[i], addr pat[0], chunk)
else:
for j in 0 ..< chunk:
result[i + j] = pat[j]
i += chunk
of dkPRG:
# Keccak(counter | seed) stream; reproducible across runs.
var off = 0
var ctr: uint64 = 0
var outblk: array[32, byte]
while off < size:
var ctx: keccak.keccak256
ctx.update(cast[array[8, byte]](ctr)) # little-endian ctr bytes
ctx.update(seed)
let digest = ctx.finish()
# Write 32 bytes at a time
when compiles(copyMem):
copyMem(addr outblk[0], unsafeAddr digest.data[0], 32)
else:
for i in 0 ..< 31: outblk[i] = digest.data[i]
let chunk = min(32, size - off)
when compiles(copyMem):
copyMem(addr result[off], addr outblk[0], chunk)
else:
for i in 0 ..< chunk: result[off + i] = outblk[i]
inc ctr
inc off, chunk
# Suggested EL-like sizes (bytes)
const BlockSizes = [
32 * 1024, # 32 KiB
64 * 1024, # 64 KiB
128 * 1024, # 128 KiB
256 * 1024, # 256 KiB
512 * 1024, # 512 KiB
1 * 1024 * 1024, # 1 MiB (stress)
2 * 1024 * 1024 # 2 MiB (stress)
]
when isMainModule:
let inputs = [
("32B", INPUT_32_BYTE.toBytesOnce()),
("64B", INPUT_64_BYTE.toBytesOnce()),
("128B", INPUT_128_BYTE.toBytesOnce()),
("256B", INPUT_256_BYTE.toBytesOnce())
]
echo "== Keccak Bench: nimcrypto vs constantine =="
for (label, buf) in inputs:
let iters = chooseIters(buf.len)
bench(&"nimcrypto {label}", keccak_nimcrypto, buf, iters)
bench(&"constantine {label}", keccak_constantine, buf, iters)
for sz in BlockSizes:
for kind in [dkZeros, dkPattern, dkPRG]:
let label =
(case kind
of dkZeros: "zeros"
of dkPattern: "pattern"
of dkPRG: "prg")
let buf = genData(kind, sz)
let iters = chooseIters(buf.len) # reuse your chooseIters()
bench(fmt"nimcrypto {label} {sz div 1024}KiB", keccak_nimcrypto, buf, iters)
bench(fmt"constantine {label} {sz div 1024}KiB", keccak_constantine, buf, iters)
```
## Results
Machine: ✨ Intel Ultra 7 255H (16) @ 5.100GHz (Nim vendor build)
Build: -d:release (Nimbus vendored Nim)
Hasher: Keccak-256 (SHA-3 variant used in Ethereum)
| Size | Content | Nimcrypto (ns/op) | Constantine (ns/op) | Δ Nimcrypto→Const (%) |
| ------: | :------ | ----------------: | ------------------: | --------------------: |
| 32 B | fixed | 304.6 | 203.4 | **+49.8 %** faster |
| 64 B | fixed | 278.9 | 204.4 | **+36.5 %** faster |
| 128 B | fixed | 304.4 | 218.7 | **+39.2 %** faster |
| 320 B* | fixed | 868.8 | 607.4 | **+43.0 %** faster |
| 32 KiB | zeros | 71 589 | 45 445 | **+57.5 %** faster |
| 32 KiB | pattern | 68 415 | 47 472 | **+44.1 %** faster |
| 32 KiB | PRG | 71 645 | 49 035 | **+46.2 %** faster |
| 64 KiB | zeros | 152 737 | 100 477 | **+52.0 %** faster |
| 64 KiB | pattern | 144 369 | 97 439 | **+48.1 %** faster |
| 64 KiB | PRG | 145 045 | 99 106 | **+46.4 %** faster |
| 128 KiB | zeros | 288 791 | 195 219 | **+47.9 %** faster |
| 128 KiB | pattern | 287 487 | 195 379 | **+47.1 %** faster |
| 128 KiB | PRG | 294 993 | 203 853 | **+44.7 %** faster |
| 256 KiB | zeros | 527 986 | 381 419 | **+38.5 %** faster |
| 256 KiB | pattern | 606 946 | 417 926 | **+45.2 %** faster |
| 256 KiB | PRG | 590 096 | 400 158 | **+47.5 %** faster |
| 512 KiB | zeros | 1 170 839 | 800 312 | **+46.3 %** faster |
| 512 KiB | pattern | 1 158 803 | 786 115 | **+47.4 %** faster |
| 512 KiB | PRG | 1 156 918 | 781 385 | **+48.1 %** faster |
| 1 MiB | zeros | 2 332 937 | 1 559 729 | **+49.5 %** faster |
| 1 MiB | pattern | 2 293 003 | 1 570 736 | **+45.9 %** faster |
| 1 MiB | PRG | 2 350 418 | 1 378 295 | **+70.5 %** faster |
| 2 MiB | zeros | 4 211 437 | 2 938 160 | **+43.4 %** faster |
| 2 MiB | pattern | 4 393 577 | 3 015 027 | **+45.8 %** faster |
| 2 MiB | PRG | 4 482 156 | 3 125 608 | **+43.4 %** faster |