| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- package benchmarks
-
- import (
- "bufio"
- "bytes"
- "crypto/rand"
- "encoding/base64"
- "fmt"
- "runtime"
- "testing"
- "time"
- "unsafe"
- )
-
- // =========================================================================
- // 1. TLS connPayload: bufio.NewReaderSize(conn, 4096) + bytes.Buffer.Grow(4096)
- // =========================================================================
-
- // connPayload mirrors tls/conn.go's connPayload struct.
- type connPayload struct {
- readBuf bytes.Buffer
- connBuffered *bufio.Reader
- read bool
- write bool
- }
-
- func newConnPayload() *connPayload {
- p := &connPayload{
- connBuffered: bufio.NewReaderSize(nil, 4096),
- read: true,
- write: true,
- }
- p.readBuf.Grow(4096)
- return p
- }
-
- func BenchmarkTLSConnPayload(b *testing.B) {
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- _ = newConnPayload()
- }
- }
-
- func TestTLSConnPayloadHeapCost(t *testing.T) {
- const N = 1000
- runtime.GC()
- var m1, m2 runtime.MemStats
- runtime.ReadMemStats(&m1)
-
- payloads := make([]*connPayload, N)
- for i := 0; i < N; i++ {
- payloads[i] = newConnPayload()
- }
-
- runtime.ReadMemStats(&m2)
- totalBytes := m2.TotalAlloc - m1.TotalAlloc
- perConn := totalBytes / N
-
- fmt.Printf("\n=== TLS connPayload heap cost ===\n")
- fmt.Printf(" Struct size (shallow): %d bytes\n", unsafe.Sizeof(connPayload{}))
- fmt.Printf(" bufio.Reader size: %d bytes (struct) + 4096 (buf)\n", unsafe.Sizeof(bufio.Reader{}))
- fmt.Printf(" Total alloc for %d conns: %d bytes (%.1f KB)\n", N, totalBytes, float64(totalBytes)/1024)
- fmt.Printf(" Per connection: %d bytes (%.1f KB)\n", perConn, float64(perConn)/1024)
- fmt.Printf(" At 1000 conns: %.1f MB\n", float64(perConn)*1000/1024/1024)
- fmt.Printf(" At 2000 conns: %.1f MB\n", float64(perConn)*2000/1024/1024)
-
- // Keep alive to prevent GC
- runtime.KeepAlive(payloads)
- }
-
- // =========================================================================
- // 2. EventTraffic allocations
- // =========================================================================
-
- // eventBase mirrors mtglib/events.go
- type eventBase struct {
- streamID string
- timestamp time.Time
- }
-
- // EventTraffic mirrors mtglib/events.go
- type EventTraffic struct {
- eventBase
- Traffic uint
- IsRead bool
- }
-
- func NewEventTraffic(streamID string, traffic uint, isRead bool) EventTraffic {
- return EventTraffic{
- eventBase: eventBase{
- timestamp: time.Now(),
- streamID: streamID,
- },
- Traffic: traffic,
- IsRead: isRead,
- }
- }
-
- func BenchmarkEventTraffic(b *testing.B) {
- streamID := "dGVzdC1zdHJlYW0taWQ"
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- _ = NewEventTraffic(streamID, 1024, true)
- }
- }
-
- // BenchmarkEventTrafficInterface tests if passing EventTraffic through an
- // interface causes heap escape.
- func BenchmarkEventTrafficInterface(b *testing.B) {
- streamID := "dGVzdC1zdHJlYW0taWQ"
- b.ReportAllocs()
- var sink interface{}
- for i := 0; i < b.N; i++ {
- sink = NewEventTraffic(streamID, 1024, true)
- }
- runtime.KeepAlive(sink)
- }
-
- func TestEventTrafficAllocRate(t *testing.T) {
- streamID := "dGVzdC1zdHJlYW0taWQ"
- const iterations = 100000
-
- runtime.GC()
- var m1, m2 runtime.MemStats
- runtime.ReadMemStats(&m1)
-
- var sink interface{}
- for i := 0; i < iterations; i++ {
- // Simulate what connTraffic.Read does: create event and pass to Send
- sink = NewEventTraffic(streamID, 1024, true)
- }
-
- runtime.ReadMemStats(&m2)
- totalBytes := m2.TotalAlloc - m1.TotalAlloc
- totalAllocs := m2.Mallocs - m1.Mallocs
-
- fmt.Printf("\n=== EventTraffic allocation rate ===\n")
- fmt.Printf(" Struct size: %d bytes\n", unsafe.Sizeof(EventTraffic{}))
- fmt.Printf(" eventBase size: %d bytes\n", unsafe.Sizeof(eventBase{}))
- fmt.Printf(" Total alloc for %d events: %d bytes (%.1f KB)\n", iterations, totalBytes, float64(totalBytes)/1024)
- fmt.Printf(" Per event: %d bytes\n", totalBytes/iterations)
- fmt.Printf(" Heap allocs: %d (%.2f per event)\n", totalAllocs, float64(totalAllocs)/float64(iterations))
- fmt.Printf(" NOTE: Each Read+Write on a connection creates 2 events.\n")
- fmt.Printf(" At 1000 conns * 100 ops/s: %.1f MB/s event alloc\n",
- float64(totalBytes)/float64(iterations)*1000*100*2/1024/1024)
- fmt.Printf(" At 2000 conns * 100 ops/s: %.1f MB/s event alloc\n",
- float64(totalBytes)/float64(iterations)*2000*100*2/1024/1024)
-
- runtime.KeepAlive(sink)
- }
-
- // =========================================================================
- // 3. connRewind buffer (bytes.Buffer for handshake recording)
- // =========================================================================
-
- func BenchmarkConnRewindBuffer(b *testing.B) {
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- var buf bytes.Buffer
- // Simulate TLS ClientHello being recorded. Typical ClientHello
- // is 200-600 bytes; we use 512 as a representative size.
- data := make([]byte, 512)
- buf.Write(data)
- _ = buf.Bytes()
- }
- }
-
- func TestConnRewindBufferCost(t *testing.T) {
- // Measure bytes.Buffer overhead for various handshake sizes
- sizes := []int{256, 512, 768, 1024, 2048}
-
- fmt.Printf("\n=== connRewind buffer cost ===\n")
- fmt.Printf(" bytes.Buffer struct size: %d bytes\n", unsafe.Sizeof(bytes.Buffer{}))
-
- for _, size := range sizes {
- const N = 1000
- runtime.GC()
- var m1, m2 runtime.MemStats
- runtime.ReadMemStats(&m1)
-
- bufs := make([]bytes.Buffer, N)
- data := make([]byte, size)
- for i := 0; i < N; i++ {
- bufs[i].Write(data)
- }
-
- runtime.ReadMemStats(&m2)
- totalBytes := m2.TotalAlloc - m1.TotalAlloc
- // Subtract the cost of the data slice and bufs slice themselves
- perConn := totalBytes / N
-
- fmt.Printf(" Handshake %4d bytes -> buffer alloc per conn: %d bytes\n", size, perConn)
- runtime.KeepAlive(bufs)
- }
-
- // Estimate at connection scale with typical 512-byte handshake
- const typicalSize = 512
- const N = 1000
- runtime.GC()
- var m1, m2 runtime.MemStats
- runtime.ReadMemStats(&m1)
-
- bufs := make([]bytes.Buffer, N)
- data := make([]byte, typicalSize)
- for i := 0; i < N; i++ {
- bufs[i].Write(data)
- }
-
- runtime.ReadMemStats(&m2)
- perConn := (m2.TotalAlloc - m1.TotalAlloc) / N
-
- fmt.Printf(" At 1000 conns (512B handshake): %.1f MB\n", float64(perConn)*1000/1024/1024)
- fmt.Printf(" At 2000 conns (512B handshake): %.1f MB\n", float64(perConn)*2000/1024/1024)
-
- runtime.KeepAlive(bufs)
- }
-
- // =========================================================================
- // 4. streamID generation: make([]byte, 16) + base64 encoding
- // =========================================================================
-
- const ConnectionIDBytesLength = 16
-
- func generateStreamIDHeap() string {
- connIDBytes := make([]byte, ConnectionIDBytesLength) // heap alloc
- rand.Read(connIDBytes) //nolint: errcheck
- return base64.RawURLEncoding.EncodeToString(connIDBytes) // heap alloc
- }
-
- func generateStreamIDStack() string {
- var connIDBytes [ConnectionIDBytesLength]byte // stack
- rand.Read(connIDBytes[:]) //nolint: errcheck
- return base64.RawURLEncoding.EncodeToString(connIDBytes[:])
- }
-
- func BenchmarkStreamIDHeap(b *testing.B) {
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- _ = generateStreamIDHeap()
- }
- }
-
- func BenchmarkStreamIDStack(b *testing.B) {
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- _ = generateStreamIDStack()
- }
- }
-
- func TestStreamIDAllocCost(t *testing.T) {
- const N = 10000
-
- // Heap version (current code)
- runtime.GC()
- var m1, m2 runtime.MemStats
- runtime.ReadMemStats(&m1)
- heapIDs := make([]string, N)
- for i := 0; i < N; i++ {
- heapIDs[i] = generateStreamIDHeap()
- }
- runtime.ReadMemStats(&m2)
- heapTotal := m2.TotalAlloc - m1.TotalAlloc
- heapPer := heapTotal / N
-
- // Stack version (proposed)
- runtime.GC()
- runtime.ReadMemStats(&m1)
- stackIDs := make([]string, N)
- for i := 0; i < N; i++ {
- stackIDs[i] = generateStreamIDStack()
- }
- runtime.ReadMemStats(&m2)
- stackTotal := m2.TotalAlloc - m1.TotalAlloc
- stackPer := stackTotal / N
-
- fmt.Printf("\n=== streamID generation cost ===\n")
- fmt.Printf(" Heap version (make([]byte,16) + base64):\n")
- fmt.Printf(" Per call: %d bytes\n", heapPer)
- fmt.Printf(" At 1000 conns: %.1f KB\n", float64(heapPer)*1000/1024)
- fmt.Printf(" At 2000 conns: %.1f KB\n", float64(heapPer)*2000/1024)
- fmt.Printf(" Stack version (var buf [16]byte + base64):\n")
- fmt.Printf(" Per call: %d bytes\n", stackPer)
- fmt.Printf(" At 1000 conns: %.1f KB\n", float64(stackPer)*1000/1024)
- fmt.Printf(" At 2000 conns: %.1f KB\n", float64(stackPer)*2000/1024)
- fmt.Printf(" Savings per call: %d bytes (%.0f%%)\n", heapPer-stackPer,
- float64(heapPer-stackPer)/float64(heapPer)*100)
-
- runtime.KeepAlive(heapIDs)
- runtime.KeepAlive(stackIDs)
- }
-
- // =========================================================================
- // Combined summary
- // =========================================================================
-
- func TestCombinedSummary(t *testing.T) {
- const N = 1000
-
- // 1. TLS connPayload
- runtime.GC()
- var m1, m2 runtime.MemStats
- runtime.ReadMemStats(&m1)
- payloads := make([]*connPayload, N)
- for i := 0; i < N; i++ {
- payloads[i] = newConnPayload()
- }
- runtime.ReadMemStats(&m2)
- tlsPerConn := (m2.TotalAlloc - m1.TotalAlloc) / N
-
- // 2. connRewind (512 byte handshake)
- runtime.GC()
- runtime.ReadMemStats(&m1)
- bufs := make([]bytes.Buffer, N)
- data := make([]byte, 512)
- for i := 0; i < N; i++ {
- bufs[i].Write(data)
- }
- runtime.ReadMemStats(&m2)
- rewindPerConn := (m2.TotalAlloc - m1.TotalAlloc) / N
-
- // 3. streamID (heap)
- runtime.GC()
- runtime.ReadMemStats(&m1)
- ids := make([]string, N)
- for i := 0; i < N; i++ {
- ids[i] = generateStreamIDHeap()
- }
- runtime.ReadMemStats(&m2)
- streamIDPerConn := (m2.TotalAlloc - m1.TotalAlloc) / N
-
- // 4. EventTraffic per op (interface escape)
- runtime.GC()
- runtime.ReadMemStats(&m1)
- var sink interface{}
- for i := 0; i < N; i++ {
- sink = NewEventTraffic("test", 1024, true)
- }
- runtime.ReadMemStats(&m2)
- eventPer := (m2.TotalAlloc - m1.TotalAlloc) / N
-
- totalPerConn := tlsPerConn + rewindPerConn + streamIDPerConn
-
- fmt.Printf("\n")
- fmt.Printf("╔══════════════════════════════════════════════════════════╗\n")
- fmt.Printf("║ PER-CONNECTION ALLOCATION SUMMARY ║\n")
- fmt.Printf("╠══════════════════════════════════════════════════════════╣\n")
- fmt.Printf("║ Component │ Per Conn │ 1000 │ 2000 ║\n")
- fmt.Printf("╠════════════════════════╪═══════════╪══════════╪═════════╣\n")
- fmt.Printf("║ TLS connPayload │ %5d B │ %5.1f MB │ %5.1f MB║\n",
- tlsPerConn, float64(tlsPerConn)*1000/1024/1024, float64(tlsPerConn)*2000/1024/1024)
- fmt.Printf("║ connRewind (512B hs) │ %5d B │ %5.1f MB │ %5.1f MB║\n",
- rewindPerConn, float64(rewindPerConn)*1000/1024/1024, float64(rewindPerConn)*2000/1024/1024)
- fmt.Printf("║ streamID generation │ %5d B │ %5.1f KB │ %5.1f KB║\n",
- streamIDPerConn, float64(streamIDPerConn)*1000/1024, float64(streamIDPerConn)*2000/1024)
- fmt.Printf("╠════════════════════════╪═══════════╪══════════╪═════════╣\n")
- fmt.Printf("║ TOTAL (one-time/conn) │ %5d B │ %5.1f MB │ %5.1f MB║\n",
- totalPerConn, float64(totalPerConn)*1000/1024/1024, float64(totalPerConn)*2000/1024/1024)
- fmt.Printf("╠════════════════════════╪═══════════╪══════════╪═════════╣\n")
- fmt.Printf("║ EventTraffic (per op) │ %5d B │ ongoing │ ongoing ║\n", eventPer)
- fmt.Printf("║ (rate at 100 ops/s) │ │ %5.1f MB/s ║\n",
- float64(eventPer)*1000*100*2/1024/1024)
- fmt.Printf("╚══════════════════════════════════════════════════════════╝\n")
-
- runtime.KeepAlive(payloads)
- runtime.KeepAlive(bufs)
- runtime.KeepAlive(ids)
- runtime.KeepAlive(sink)
- }
|