mirror of
https://github.com/luxfi/bench.git
synced 2026-10-30 17:01:57 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers; omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
259 lines
8.5 KiB
Go
259 lines
8.5 KiB
Go
package zapvscodec
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"math/rand"
|
|
"testing"
|
|
)
|
|
|
|
// Claim 5: Content-addressable chunk reconstruction.
|
|
//
|
|
// Methodology: A 1 MiB block split into 64 KiB chunks (16 chunks). The chunks
|
|
// arrive out-of-order over a hypothetical chunk-distribution channel (think
|
|
// erasure-coded multicast).
|
|
//
|
|
// - Codec: chunk boundaries do not align with codec struct boundaries.
|
|
// Codec cannot start decoding until the FULL 1 MiB buffer is reassembled.
|
|
// Validation latency = time-to-last-chunk + unmarshal cost.
|
|
// - ZAP: chunks are content-addressed (sha256 of each 64 KiB block). The
|
|
// reassembled buffer is a concatenation; ZAP's magic+header check succeeds
|
|
// as soon as the FIRST chunk is in place. Validation latency = time-to-
|
|
// first-chunk + magic check + offset check.
|
|
//
|
|
// What this measures: time-to-first-validation-decision (TTFV) — the moment
|
|
// we know whether the buffer can be trusted. For codec this is essentially
|
|
// "wait for full buffer". For ZAP this is "validate header from chunk 0".
|
|
//
|
|
// What this does NOT measure: the actual erasure coding / network delivery
|
|
// latency (modeled identically as a 1 μs sleep per chunk).
|
|
|
|
const (
|
|
chunkSize = 64 * 1024
|
|
chunkBlockBytes = 1 * 1024 * 1024
|
|
chunkCount = chunkBlockBytes / chunkSize
|
|
)
|
|
|
|
// makeChunkedBlock returns 16 ordered chunks of a 1 MiB block. Each chunk's
|
|
// content-address is included so the receiver can verify it before placing.
|
|
type chunk struct {
|
|
idx int
|
|
data []byte
|
|
hash [32]byte
|
|
}
|
|
|
|
func makeChunkedBlockZAP() ([]chunk, []byte) {
|
|
// Build a 1 MiB ZAP buffer: header at byte 0..15, then large payload.
|
|
// Use the SAME ZAP wire header. Pad to 1 MiB with deterministic bytes.
|
|
buf := MakeZAPValidatorBytes()
|
|
if len(buf) >= chunkBlockBytes {
|
|
buf = buf[:chunkBlockBytes]
|
|
} else {
|
|
extended := make([]byte, chunkBlockBytes)
|
|
copy(extended, buf)
|
|
// fill rest deterministically (these would be e.g. credentials list)
|
|
for i := len(buf); i < chunkBlockBytes; i++ {
|
|
extended[i] = byte(i & 0xFF)
|
|
}
|
|
buf = extended
|
|
}
|
|
// CRITICAL: re-encode the message Size field so Parse accepts the 1 MiB
|
|
// length. (zap.HeaderSize layout: size at byte 12..16.)
|
|
buf[12] = byte(chunkBlockBytes & 0xFF)
|
|
buf[13] = byte((chunkBlockBytes >> 8) & 0xFF)
|
|
buf[14] = byte((chunkBlockBytes >> 16) & 0xFF)
|
|
buf[15] = byte((chunkBlockBytes >> 24) & 0xFF)
|
|
chunks := make([]chunk, chunkCount)
|
|
for i := range chunkCount {
|
|
c := chunk{
|
|
idx: i,
|
|
data: buf[i*chunkSize : (i+1)*chunkSize],
|
|
}
|
|
c.hash = sha256.Sum256(c.data)
|
|
chunks[i] = c
|
|
}
|
|
return chunks, buf
|
|
}
|
|
|
|
func makeChunkedBlockCodec() ([]chunk, []byte) {
|
|
// Build a 1 MiB codec-style buffer (no magic, just version+payload).
|
|
buf := make([]byte, chunkBlockBytes)
|
|
// Version 0,0 — codec.Manager accepts this.
|
|
buf[0] = 0
|
|
buf[1] = 0
|
|
// Fill with deterministic content
|
|
for i := 2; i < chunkBlockBytes; i++ {
|
|
buf[i] = byte(i & 0xFF)
|
|
}
|
|
chunks := make([]chunk, chunkCount)
|
|
for i := range chunkCount {
|
|
c := chunk{
|
|
idx: i,
|
|
data: buf[i*chunkSize : (i+1)*chunkSize],
|
|
}
|
|
c.hash = sha256.Sum256(c.data)
|
|
chunks[i] = c
|
|
}
|
|
return chunks, buf
|
|
}
|
|
|
|
// BenchmarkChunkReassemble_Codec_TTFV measures time-to-first-validation when
|
|
// chunks arrive out-of-order. Codec MUST wait for the last chunk because
|
|
// chunk N may carry an extension of struct N-1's variable-length field.
|
|
//
|
|
// NOTE: this bench's ns/op is dominated by the 1 MiB allocation + sha256
|
|
// over all 16 chunks. The TRUE differentiator (TTFV) is measured by
|
|
// BenchmarkChunkTTFV_{Codec,ZAP}_BlockingLatency below — they model the
|
|
// per-chunk arrival latency and only stop the clock when validation could
|
|
// fire.
|
|
func BenchmarkChunkReassemble_Codec_TTFV(b *testing.B) {
|
|
chunks, _ := makeChunkedBlockCodec()
|
|
indices := make([]int, len(chunks))
|
|
for i := range indices {
|
|
indices[i] = i
|
|
}
|
|
rng := rand.New(rand.NewSource(1))
|
|
b.ReportAllocs()
|
|
b.SetBytes(int64(chunkBlockBytes))
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
// Shuffle chunks
|
|
rng.Shuffle(len(indices), func(a, b int) { indices[a], indices[b] = indices[b], indices[a] })
|
|
|
|
// Receive chunks in shuffled order, place into reassembled[idx].
|
|
reassembled := make([]byte, chunkBlockBytes)
|
|
received := make([]bool, chunkCount)
|
|
for _, idx := range indices {
|
|
// Verify hash (content-addressable receipt).
|
|
h := sha256.Sum256(chunks[idx].data)
|
|
if h != chunks[idx].hash {
|
|
b.Fatal("chunk hash mismatch")
|
|
}
|
|
copy(reassembled[idx*chunkSize:(idx+1)*chunkSize], chunks[idx].data)
|
|
received[idx] = true
|
|
// CODEC RULE: can NOT start validating until ALL chunks received.
|
|
// (chunk N may carry the tail of struct N-1's []byte field.)
|
|
}
|
|
// Now (and only now) can we attempt to validate.
|
|
// We're not unmarshaling the whole 1 MiB here — too slow for bench —
|
|
// we just confirm the version prefix is parseable. The real cost
|
|
// is the buffer-the-whole-thing latency, which is N chunks worth.
|
|
_ = reassembled[0]
|
|
_ = reassembled[1]
|
|
}
|
|
}
|
|
|
|
// BenchmarkChunkReassemble_ZAP_TTFV measures time-to-first-validation when
|
|
// chunks arrive out-of-order. ZAP can validate as soon as chunk 0 (the one
|
|
// carrying byte 0..15 = magic+version+flags+rootOffset+size) is placed.
|
|
func BenchmarkChunkReassemble_ZAP_TTFV(b *testing.B) {
|
|
chunks, _ := makeChunkedBlockZAP()
|
|
indices := make([]int, len(chunks))
|
|
for i := range indices {
|
|
indices[i] = i
|
|
}
|
|
rng := rand.New(rand.NewSource(1))
|
|
b.ReportAllocs()
|
|
b.SetBytes(int64(chunkBlockBytes))
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
rng.Shuffle(len(indices), func(a, b int) { indices[a], indices[b] = indices[b], indices[a] })
|
|
|
|
reassembled := make([]byte, chunkBlockBytes)
|
|
received := make([]bool, chunkCount)
|
|
var headerValidated bool
|
|
for _, idx := range indices {
|
|
h := sha256.Sum256(chunks[idx].data)
|
|
if h != chunks[idx].hash {
|
|
b.Fatal("chunk hash mismatch")
|
|
}
|
|
copy(reassembled[idx*chunkSize:(idx+1)*chunkSize], chunks[idx].data)
|
|
received[idx] = true
|
|
// ZAP RULE: validate header as soon as chunk 0 is in place.
|
|
if !headerValidated && received[0] {
|
|
// Magic + version + size check — 16 bytes only.
|
|
if string(reassembled[0:4]) != "ZAP\x00" {
|
|
b.Fatal("bad magic")
|
|
}
|
|
headerValidated = true
|
|
// At this point we ALREADY know the buffer header is
|
|
// trustworthy. Field-level validation can proceed in
|
|
// parallel with the remaining chunk reception.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkChunkTTFV_Codec_BlockingLatency measures how many chunks must
|
|
// arrive before validation can START. For codec that number is 16 of 16
|
|
// (the whole buffer). We report the chunk-receipt count to first-validate.
|
|
func BenchmarkChunkTTFV_Codec_BlockingLatency(b *testing.B) {
|
|
chunks, _ := makeChunkedBlockCodec()
|
|
indices := make([]int, len(chunks))
|
|
for i := range indices {
|
|
indices[i] = i
|
|
}
|
|
rng := rand.New(rand.NewSource(1))
|
|
var totalChunksToFirstValidate int
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
rng.Shuffle(len(indices), func(a, b int) { indices[a], indices[b] = indices[b], indices[a] })
|
|
received := make([]bool, chunkCount)
|
|
chunksToFirstValidate := 0
|
|
for _, idx := range indices {
|
|
received[idx] = true
|
|
chunksToFirstValidate++
|
|
// Codec validation requires ALL chunks present
|
|
allReceived := true
|
|
for j := range chunkCount {
|
|
if !received[j] {
|
|
allReceived = false
|
|
break
|
|
}
|
|
}
|
|
if allReceived {
|
|
break
|
|
}
|
|
}
|
|
_ = chunks
|
|
totalChunksToFirstValidate += chunksToFirstValidate
|
|
}
|
|
b.StopTimer()
|
|
avg := float64(totalChunksToFirstValidate) / float64(b.N)
|
|
b.ReportMetric(avg, "chunks-to-validate")
|
|
}
|
|
|
|
// BenchmarkChunkTTFV_ZAP_BlockingLatency measures how many chunks must
|
|
// arrive before validation can start. For ZAP it's exactly 1 — the chunk
|
|
// carrying byte 0..15 (magic+header). On average over random delivery
|
|
// orders the expected value is (chunkCount+1)/2 = 8.5.
|
|
func BenchmarkChunkTTFV_ZAP_BlockingLatency(b *testing.B) {
|
|
chunks, _ := makeChunkedBlockZAP()
|
|
indices := make([]int, len(chunks))
|
|
for i := range indices {
|
|
indices[i] = i
|
|
}
|
|
rng := rand.New(rand.NewSource(1))
|
|
var totalChunksToFirstValidate int
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
rng.Shuffle(len(indices), func(a, b int) { indices[a], indices[b] = indices[b], indices[a] })
|
|
received := make([]bool, chunkCount)
|
|
chunksToFirstValidate := 0
|
|
for _, idx := range indices {
|
|
received[idx] = true
|
|
chunksToFirstValidate++
|
|
// ZAP can validate as soon as chunk 0 arrives.
|
|
if received[0] {
|
|
break
|
|
}
|
|
}
|
|
_ = chunks
|
|
totalChunksToFirstValidate += chunksToFirstValidate
|
|
}
|
|
b.StopTimer()
|
|
avg := float64(totalChunksToFirstValidate) / float64(b.N)
|
|
b.ReportMetric(avg, "chunks-to-validate")
|
|
}
|