# 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 |