mirror of
https://github.com/luxfi/zap.git
synced 2026-07-27 05:54:26 +00:00
- Zero-copy binary serialization (2.9ns parse, 0 allocations) - 17x faster than MCP JSON-RPC, 11x less memory, 29x fewer allocations - mDNS peer discovery with automatic mesh networking - Request/response correlation for async RPC calls - MCP bridge for auto-discovering and accelerating MCP servers - 20 example tools (file, search, code, git, API) - Consensus reaching agreement in ~450µs - Environmental: 96% energy reduction at scale (~91 tonnes CO2/year saved) Benchmarks (Apple M1 Max): BenchmarkZAPParse: 2.9 ns/op, 0 B/op, 0 allocs/op BenchmarkZAPToolCall: 322 ns/op, 256 B/op, 2 allocs/op BenchmarkMCPToolCall: 5579 ns/op, 2826 B/op, 58 allocs/op
293 lines
9.4 KiB
Go
293 lines
9.4 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package zap
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"runtime"
|
|
"testing"
|
|
)
|
|
|
|
// ============================================================================
|
|
// Real Memory Usage Profiling
|
|
// ============================================================================
|
|
|
|
func getMemStats() runtime.MemStats {
|
|
runtime.GC()
|
|
runtime.GC()
|
|
var m runtime.MemStats
|
|
runtime.ReadMemStats(&m)
|
|
return m
|
|
}
|
|
|
|
func formatBytes(b uint64) string {
|
|
const unit = 1024
|
|
if b < unit {
|
|
return fmt.Sprintf("%d B", b)
|
|
}
|
|
div, exp := uint64(unit), 0
|
|
for n := b / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.2f %cB", float64(b)/float64(div), "KMGTPE"[exp])
|
|
}
|
|
|
|
// TestMemoryUsageComparison shows real heap memory differences
|
|
func TestMemoryUsageComparison(t *testing.T) {
|
|
const numOps = 100000
|
|
|
|
t.Log("=== Real Memory Usage Comparison ===")
|
|
t.Log("Simulating 100,000 tool call round-trips\n")
|
|
|
|
// ========== MCP-style Memory Usage ==========
|
|
t.Log("--- MCP JSON-RPC Style ---")
|
|
runtime.GC()
|
|
runtime.GC()
|
|
beforeMCP := getMemStats()
|
|
|
|
// Simulate MCP tool calls (keep some data alive to measure heap)
|
|
mcpResults := make([][]byte, 0, numOps)
|
|
for i := 0; i < numOps; i++ {
|
|
result, _ := simulateMCPToolCall("search_tool", map[string]string{
|
|
"query": "test query",
|
|
"limit": "10",
|
|
})
|
|
if i%10 == 0 { // Keep 10% of results to simulate real usage
|
|
mcpResults = append(mcpResults, result)
|
|
}
|
|
}
|
|
|
|
afterMCP := getMemStats()
|
|
mcpHeapAlloc := afterMCP.TotalAlloc - beforeMCP.TotalAlloc
|
|
mcpHeapInUse := afterMCP.HeapInuse - beforeMCP.HeapInuse
|
|
mcpMallocs := afterMCP.Mallocs - beforeMCP.Mallocs
|
|
|
|
t.Logf("Total Allocated: %s", formatBytes(mcpHeapAlloc))
|
|
t.Logf("Heap In Use: %s", formatBytes(mcpHeapInUse))
|
|
t.Logf("Malloc Count: %d", mcpMallocs)
|
|
t.Logf("Bytes/Op: %d", mcpHeapAlloc/numOps)
|
|
t.Logf("Mallocs/Op: %d", mcpMallocs/numOps)
|
|
|
|
// Clear MCP results
|
|
mcpResults = nil
|
|
runtime.GC()
|
|
runtime.GC()
|
|
|
|
// ========== ZAP-style Memory Usage ==========
|
|
t.Log("\n--- ZAP Zero-Copy Style ---")
|
|
runtime.GC()
|
|
runtime.GC()
|
|
beforeZAP := getMemStats()
|
|
|
|
// Simulate ZAP tool calls
|
|
zapResults := make([][]byte, 0, numOps)
|
|
for i := 0; i < numOps; i++ {
|
|
result, _ := simulateZAPToolCall(uint32(i), "search_tool")
|
|
if i%10 == 0 { // Keep 10% of results
|
|
zapResults = append(zapResults, result)
|
|
}
|
|
}
|
|
|
|
afterZAP := getMemStats()
|
|
zapHeapAlloc := afterZAP.TotalAlloc - beforeZAP.TotalAlloc
|
|
zapHeapInUse := afterZAP.HeapInuse - beforeZAP.HeapInuse
|
|
zapMallocs := afterZAP.Mallocs - beforeZAP.Mallocs
|
|
|
|
t.Logf("Total Allocated: %s", formatBytes(zapHeapAlloc))
|
|
t.Logf("Heap In Use: %s", formatBytes(zapHeapInUse))
|
|
t.Logf("Malloc Count: %d", zapMallocs)
|
|
t.Logf("Bytes/Op: %d", zapHeapAlloc/numOps)
|
|
t.Logf("Mallocs/Op: %d", zapMallocs/numOps)
|
|
|
|
// Clear ZAP results
|
|
zapResults = nil
|
|
|
|
// ========== Summary ==========
|
|
t.Log("\n=== Efficiency Summary ===")
|
|
memSavings := float64(mcpHeapAlloc-zapHeapAlloc) / float64(mcpHeapAlloc) * 100
|
|
mallocSavings := float64(mcpMallocs-zapMallocs) / float64(mcpMallocs) * 100
|
|
|
|
t.Logf("Memory Saved: %s (%.1f%%)", formatBytes(mcpHeapAlloc-zapHeapAlloc), memSavings)
|
|
t.Logf("Allocations Saved: %d (%.1f%%)", mcpMallocs-zapMallocs, mallocSavings)
|
|
t.Logf("Memory Ratio: %.1fx less", float64(mcpHeapAlloc)/float64(zapHeapAlloc))
|
|
t.Logf("Malloc Ratio: %.1fx fewer", float64(mcpMallocs)/float64(zapMallocs))
|
|
|
|
// Energy/Carbon estimate (rough: 1 GB memory = ~0.5W, allocations cause cache misses)
|
|
t.Log("\n=== Environmental Impact (per 1M ops) ===")
|
|
mcpMemMB := float64(mcpHeapAlloc) * 10 / 1024 / 1024 // Scale to 1M ops
|
|
zapMemMB := float64(zapHeapAlloc) * 10 / 1024 / 1024
|
|
t.Logf("MCP Memory Footprint: %.1f MB", mcpMemMB)
|
|
t.Logf("ZAP Memory Footprint: %.1f MB", zapMemMB)
|
|
t.Logf("Memory Saved per 1M: %.1f MB", mcpMemMB-zapMemMB)
|
|
|
|
// Rough energy estimate: memory bandwidth + allocation overhead
|
|
// ~0.1 nJ per byte transferred, ~100 nJ per malloc (cache miss + syscall amortized)
|
|
mcpEnergyJ := (float64(mcpHeapAlloc)*0.1 + float64(mcpMallocs)*100) * 10 / 1e9
|
|
zapEnergyJ := (float64(zapHeapAlloc)*0.1 + float64(zapMallocs)*100) * 10 / 1e9
|
|
t.Logf("MCP Energy (est): %.3f J per 1M ops", mcpEnergyJ)
|
|
t.Logf("ZAP Energy (est): %.3f J per 1M ops", zapEnergyJ)
|
|
t.Logf("Energy Saved: %.1f%% reduction", (mcpEnergyJ-zapEnergyJ)/mcpEnergyJ*100)
|
|
|
|
// At scale
|
|
t.Log("\n=== At Scale (1B ops/day - typical AI agent cluster) ===")
|
|
dailyOps := 1e9
|
|
mcpDailyMem := mcpMemMB * dailyOps / 1e6 / 1024 // GB
|
|
zapDailyMem := zapMemMB * dailyOps / 1e6 / 1024
|
|
mcpDailyEnergy := mcpEnergyJ * dailyOps / 1e6 / 3600 // kWh
|
|
zapDailyEnergy := zapEnergyJ * dailyOps / 1e6 / 3600
|
|
co2PerKwh := 0.4 // kg CO2 per kWh (global average)
|
|
|
|
t.Logf("MCP Daily Memory: %.1f TB throughput", mcpDailyMem/1024)
|
|
t.Logf("ZAP Daily Memory: %.1f TB throughput", zapDailyMem/1024)
|
|
t.Logf("Memory Saved Daily: %.1f TB", (mcpDailyMem-zapDailyMem)/1024)
|
|
t.Logf("MCP Daily Energy: %.1f kWh", mcpDailyEnergy)
|
|
t.Logf("ZAP Daily Energy: %.1f kWh", zapDailyEnergy)
|
|
t.Logf("Energy Saved Daily: %.1f kWh (%.1f%%)", mcpDailyEnergy-zapDailyEnergy, (mcpDailyEnergy-zapDailyEnergy)/mcpDailyEnergy*100)
|
|
t.Logf("CO2 Saved Daily: %.1f kg", (mcpDailyEnergy-zapDailyEnergy)*co2PerKwh)
|
|
t.Logf("CO2 Saved Yearly: %.1f tonnes", (mcpDailyEnergy-zapDailyEnergy)*co2PerKwh*365/1000)
|
|
}
|
|
|
|
// TestGCPressure measures garbage collection impact
|
|
func TestGCPressure(t *testing.T) {
|
|
const numOps = 50000
|
|
|
|
t.Log("=== GC Pressure Comparison ===\n")
|
|
|
|
// MCP GC pressure
|
|
runtime.GC()
|
|
var mcpGCStats runtime.MemStats
|
|
runtime.ReadMemStats(&mcpGCStats)
|
|
mcpGCBefore := mcpGCStats.NumGC
|
|
|
|
for i := 0; i < numOps; i++ {
|
|
simulateMCPToolCall("tool", map[string]string{"k": "v"})
|
|
}
|
|
|
|
runtime.ReadMemStats(&mcpGCStats)
|
|
mcpGCAfter := mcpGCStats.NumGC
|
|
mcpGCRuns := mcpGCAfter - mcpGCBefore
|
|
|
|
t.Logf("MCP: %d GC runs for %d ops (1 GC per %d ops)", mcpGCRuns, numOps, numOps/max(mcpGCRuns, 1))
|
|
|
|
// ZAP GC pressure
|
|
runtime.GC()
|
|
var zapGCStats runtime.MemStats
|
|
runtime.ReadMemStats(&zapGCStats)
|
|
zapGCBefore := zapGCStats.NumGC
|
|
|
|
for i := 0; i < numOps; i++ {
|
|
simulateZAPToolCall(uint32(i), "tool")
|
|
}
|
|
|
|
runtime.ReadMemStats(&zapGCStats)
|
|
zapGCAfter := zapGCStats.NumGC
|
|
zapGCRuns := zapGCAfter - zapGCBefore
|
|
|
|
t.Logf("ZAP: %d GC runs for %d ops (1 GC per %d ops)", zapGCRuns, numOps, numOps/max(zapGCRuns, 1))
|
|
|
|
if mcpGCRuns > 0 {
|
|
t.Logf("\nZAP reduces GC pressure by %.1fx", float64(mcpGCRuns)/float64(max(zapGCRuns, 1)))
|
|
}
|
|
}
|
|
|
|
// TestRealisticAgentWorkload simulates a real agent doing tool calls
|
|
func TestRealisticAgentWorkload(t *testing.T) {
|
|
t.Log("=== Realistic Agent Workload ===")
|
|
t.Log("Simulating agent making 1000 tool calls with mixed payloads\n")
|
|
|
|
tools := []struct {
|
|
name string
|
|
args map[string]string
|
|
}{
|
|
{"search", map[string]string{"query": "find all users", "limit": "100"}},
|
|
{"read_file", map[string]string{"path": "/etc/config.json"}},
|
|
{"write_file", map[string]string{"path": "/tmp/out.txt", "content": "data"}},
|
|
{"http_get", map[string]string{"url": "https://api.example.com/data"}},
|
|
{"database_query", map[string]string{"sql": "SELECT * FROM users WHERE active=true"}},
|
|
{"shell_exec", map[string]string{"cmd": "ls -la /var/log"}},
|
|
{"image_analyze", map[string]string{"path": "/tmp/img.png", "model": "gpt-4-vision"}},
|
|
{"vector_search", map[string]string{"query": "semantic search", "k": "10"}},
|
|
{"code_complete", map[string]string{"prefix": "func main() {", "lang": "go"}},
|
|
{"translate", map[string]string{"text": "Hello world", "to": "es"}},
|
|
}
|
|
|
|
const iterations = 1000
|
|
|
|
// MCP workload
|
|
runtime.GC()
|
|
mcpBefore := getMemStats()
|
|
|
|
for i := 0; i < iterations; i++ {
|
|
tool := tools[i%len(tools)]
|
|
simulateMCPToolCall(tool.name, tool.args)
|
|
}
|
|
|
|
mcpAfter := getMemStats()
|
|
|
|
// ZAP workload
|
|
runtime.GC()
|
|
zapBefore := getMemStats()
|
|
|
|
for i := 0; i < iterations; i++ {
|
|
tool := tools[i%len(tools)]
|
|
_ = tool // ZAP uses numeric IDs
|
|
simulateZAPToolCall(uint32(i%len(tools)), tools[i%len(tools)].name)
|
|
}
|
|
|
|
zapAfter := getMemStats()
|
|
|
|
t.Logf("MCP: %s allocated, %d mallocs",
|
|
formatBytes(mcpAfter.TotalAlloc-mcpBefore.TotalAlloc),
|
|
mcpAfter.Mallocs-mcpBefore.Mallocs)
|
|
t.Logf("ZAP: %s allocated, %d mallocs",
|
|
formatBytes(zapAfter.TotalAlloc-zapBefore.TotalAlloc),
|
|
zapAfter.Mallocs-zapBefore.Mallocs)
|
|
|
|
memRatio := float64(mcpAfter.TotalAlloc-mcpBefore.TotalAlloc) / float64(zapAfter.TotalAlloc-zapBefore.TotalAlloc)
|
|
t.Logf("\nZAP uses %.1fx less memory for realistic agent workload", memRatio)
|
|
}
|
|
|
|
// Helper for simulateMCPToolCall - make sure it allocates like real MCP
|
|
func simulateMCPToolCallRealistic(toolName string, args map[string]interface{}) ([]byte, error) {
|
|
req := map[string]interface{}{
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"method": "tools/call",
|
|
"params": map[string]interface{}{
|
|
"name": toolName,
|
|
"arguments": args,
|
|
},
|
|
}
|
|
|
|
// Full JSON round-trip
|
|
reqBytes, _ := json.Marshal(req)
|
|
var serverReq map[string]interface{}
|
|
json.Unmarshal(reqBytes, &serverReq)
|
|
|
|
resp := map[string]interface{}{
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"result": map[string]interface{}{
|
|
"content": []map[string]string{
|
|
{"type": "text", "text": "Result from " + toolName},
|
|
},
|
|
},
|
|
}
|
|
|
|
respBytes, _ := json.Marshal(resp)
|
|
var clientResp map[string]interface{}
|
|
json.Unmarshal(respBytes, &clientResp)
|
|
|
|
return respBytes, nil
|
|
}
|
|
|
|
func max(a, b uint32) uint32 {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|