diff --git a/README.md b/README.md index 044895f..92a1250 100644 --- a/README.md +++ b/README.md @@ -118,11 +118,99 @@ func main() { ### Top-K Sketch -(TBD) +``` +goos: darwin +goarch: arm64 +pkg: github.com/keilerkonzept/topk/sliding +cpu: Apple M1 Pro +``` + +| Operation | K | Depth | Width | time/op | bytes/op | allocs/op | +|-----------|----:|------:|------:|------------:|---------:|------------:| +| `Add` | 10 | 3 | 1024 | 358.6 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 10 | 3 | 8192 | 375.0 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 10 | 4 | 1024 | 449.9 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 10 | 4 | 8192 | 436.0 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 100 | 3 | 1024 | 371.5 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 100 | 3 | 8192 | 387.9 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 100 | 4 | 1024 | 452.3 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 100 | 4 | 8192 | 471.4 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 10 | 3 | 1024 | 257.2 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 10 | 3 | 8192 | 232.3 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 10 | 4 | 1024 | 249.1 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 10 | 4 | 8192 | 251.2 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 100 | 3 | 1024 | 264.2 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 100 | 3 | 8192 | 227.4 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 100 | 4 | 1024 | 267.1 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 100 | 4 | 8192 | 261.3 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 10 | 3 | 1024 | 216.0 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 10 | 3 | 8192 | 215.4 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 10 | 4 | 1024 | 220.0 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 10 | 4 | 8192 | 269.3 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 100 | 3 | 1024 | 235.1 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 100 | 3 | 8192 | 277.1 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 100 | 4 | 1024 | 278.7 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 100 | 4 | 8192 | 302.2 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 10 | 3 | 1024 | 129.6 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 10 | 3 | 8192 | 98.21 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 10 | 4 | 1024 | 129.9 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 10 | 4 | 8192 | 114.3 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 100 | 3 | 1024 | 141.2 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 100 | 3 | 8192 | 140.8 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 100 | 4 | 1024 | 131.1 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 100 | 4 | 8192 | 109.8 ns/op | 0 B/op | 0 allocs/op | ### Sliding-Window Top-K Sketch -(TBD) +``` +goos: darwin +goarch: arm64 +pkg: github.com/keilerkonzept/topk/sliding +cpu: Apple M1 Pro +``` + +| Operation | K | Depth | Width | Window size | History buckets | time/op | bytes/op | allocs/op | +|-----------|----:|------:|------:|------------:|----------------:|------------:|---------:|------------:| +| `Add` | 10 | 3 | 1024 | 100 | 50 | 696.9 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 10 | 3 | 1024 | 100 | 100 | 1051 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 10 | 3 | 8192 | 100 | 50 | 784.9 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 10 | 3 | 8192 | 100 | 100 | 1146 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 100 | 3 | 1024 | 100 | 50 | 712.9 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 100 | 3 | 1024 | 100 | 100 | 1054 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 100 | 3 | 8192 | 100 | 50 | 763.3 ns/op | 0 B/op | 0 allocs/op | +| `Add` | 100 | 3 | 8192 | 100 | 100 | 1139 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 10 | 3 | 1024 | 100 | 50 | 434.9 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 10 | 3 | 1024 | 100 | 100 | 560.7 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 10 | 3 | 8192 | 100 | 50 | 501.1 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 10 | 3 | 8192 | 100 | 100 | 728.7 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 100 | 3 | 1024 | 100 | 50 | 425.6 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 100 | 3 | 1024 | 100 | 100 | 580.0 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 100 | 3 | 8192 | 100 | 50 | 497.8 ns/op | 0 B/op | 0 allocs/op | +| `Incr` | 100 | 3 | 8192 | 100 | 100 | 746.2 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 10 | 3 | 1024 | 100 | 50 | 228.5 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 10 | 3 | 1024 | 100 | 100 | 209.3 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 10 | 3 | 8192 | 100 | 50 | 234.5 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 10 | 3 | 8192 | 100 | 100 | 230.7 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 100 | 3 | 1024 | 100 | 50 | 237.5 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 100 | 3 | 1024 | 100 | 100 | 242.8 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 100 | 3 | 8192 | 100 | 50 | 246.5 ns/op | 0 B/op | 0 allocs/op | +| `Count` | 100 | 3 | 8192 | 100 | 100 | 243.4 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 10 | 3 | 1024 | 100 | 50 | 101.7 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 10 | 3 | 1024 | 100 | 100 | 104.8 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 10 | 3 | 8192 | 100 | 50 | 114.0 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 10 | 3 | 8192 | 100 | 100 | 114.5 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 100 | 3 | 1024 | 100 | 50 | 135.9 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 100 | 3 | 1024 | 100 | 100 | 118.5 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 100 | 3 | 8192 | 100 | 50 | 130.1 ns/op | 0 B/op | 0 allocs/op | +| `Query` | 100 | 3 | 8192 | 100 | 100 | 131.5 ns/op | 0 B/op | 0 allocs/op | +| `Tick` | 10 | 3 | 1024 | 100 | 50 | 4191 ns/op | 0 B/op | 0 allocs/op | +| `Tick` | 10 | 3 | 1024 | 100 | 100 | 7010 ns/op | 0 B/op | 0 allocs/op | +| `Tick` | 10 | 3 | 8192 | 100 | 50 | 28699 ns/op | 0 B/op | 0 allocs/op | +| `Tick` | 10 | 3 | 8192 | 100 | 100 | 90979 ns/op | 0 B/op | 0 allocs/op | +| `Tick` | 100 | 3 | 1024 | 100 | 50 | 6539 ns/op | 0 B/op | 0 allocs/op | +| `Tick` | 100 | 3 | 1024 | 100 | 100 | 9343 ns/op | 0 B/op | 0 allocs/op | +| `Tick` | 100 | 3 | 8192 | 100 | 50 | 31349 ns/op | 0 B/op | 0 allocs/op | +| `Tick` | 100 | 3 | 8192 | 100 | 100 | 87488 ns/op | 0 B/op | 0 allocs/op | ### Decay LUT impact diff --git a/sketch_bench_test.go b/sketch_bench_test.go new file mode 100644 index 0000000..4ecdbcf --- /dev/null +++ b/sketch_bench_test.go @@ -0,0 +1,114 @@ +package topk_test + +import ( + "fmt" + "math/rand/v2" + "testing" + + "github.com/keilerkonzept/topk" +) + +var ( + ks = []int{10, 100} + depths = []int{3, 4} + widths = []int{1024, 8192} + items = generateItems(1_000_000) +) + +func generateItems(n int) []string { + items := make([]string, n) + for i := 0; i < n; i++ { + items[i] = fmt.Sprintf("item%d", i) + } + return items +} + +// BenchmarkSketchAdd benchmarks the Add method of Sketch. +func BenchmarkSketchAdd(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d", k, depth, width), func(b *testing.B) { + sketch := topk.New(k, + topk.WithDepth(depth), + topk.WithWidth(width), + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Add(items[rand.IntN(len(items))], uint32(rand.IntN(10))) + } + }) + } + } + } +} + +// BenchmarkSketchIncr benchmarks the Incr method of Sketch. +func BenchmarkSketchIncr(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d", k, depth, width), func(b *testing.B) { + sketch := topk.New(k, + topk.WithDepth(depth), + topk.WithWidth(width), + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Incr(items[rand.IntN(len(items))]) + } + }) + } + } + } +} + +// BenchmarkSketchCount benchmarks the Count method of Sketch. +func BenchmarkSketchCount(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d", k, depth, width), func(b *testing.B) { + sketch := topk.New(k, + topk.WithDepth(depth), + topk.WithWidth(width), + ) + for _, item := range items { + sketch.Add(item, uint32(rand.IntN(10))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Count(items[rand.IntN(len(items))]) + } + }) + } + } + } +} + +// BenchmarkSketchQuery benchmarks the Query method of Sketch. +func BenchmarkSketchQuery(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d", k, depth, width), func(b *testing.B) { + sketch := topk.New(k, + topk.WithDepth(depth), + topk.WithWidth(width), + ) + for _, item := range items { + sketch.Add(item, uint32(rand.IntN(10))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Query(items[rand.IntN(len(items))]) + } + }) + } + } + } +} diff --git a/sliding/bucket.go b/sliding/bucket.go index 3cd3276..66e7c70 100644 --- a/sliding/bucket.go +++ b/sliding/bucket.go @@ -31,7 +31,7 @@ func (me *Bucket) findNonzeroMinimumCount() int { first := true var countsMin uint32 i := me.First - for range len(me.Counts) { + for range me.Counts { if i == uint32(len(me.Counts)) { i = 0 } diff --git a/sliding/sketch_bench_test.go b/sliding/sketch_bench_test.go new file mode 100644 index 0000000..c91d01a --- /dev/null +++ b/sliding/sketch_bench_test.go @@ -0,0 +1,165 @@ +package sliding_test + +import ( + "fmt" + "math/rand/v2" + "testing" + + "github.com/keilerkonzept/topk/sliding" +) + +var ( + ks = []int{10, 100} + depths = []int{3} + widths = []int{1024, 8192} + windowSizes = []int{100} + items = generateItems(1_000_000) +) + +func generateItems(n int) []string { + items := make([]string, n) + for i := 0; i < n; i++ { + items[i] = fmt.Sprintf("item%d", i) + } + return items +} + +// BenchmarkSketchAdd benchmarks the Add method of Sketch. +func BenchmarkSketchAdd(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + for _, windowSize := range windowSizes { + for _, historyLen := range []int{windowSize / 2, windowSize} { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d_WindowSize=%d_HistoryLen=%d", k, depth, width, windowSize, historyLen), func(b *testing.B) { + sketch := sliding.New(k, windowSize, + sliding.WithDepth(depth), + sliding.WithWidth(width), + sliding.WithBucketHistoryLength(historyLen), + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Add(items[rand.IntN(len(items))], uint32(rand.IntN(10))) + } + }) + } + } + } + } + } +} + +// BenchmarkSketchIncr benchmarks the Incr method of Sketch. +func BenchmarkSketchIncr(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + for _, windowSize := range windowSizes { + for _, historyLen := range []int{windowSize / 2, windowSize} { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d_WindowSize=%d_HistoryLen=%d", k, depth, width, windowSize, historyLen), func(b *testing.B) { + sketch := sliding.New(k, windowSize, + sliding.WithDepth(depth), + sliding.WithWidth(width), + sliding.WithBucketHistoryLength(historyLen), + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Incr(items[rand.IntN(len(items))]) + } + }) + } + } + } + } + } +} + +// BenchmarkSketchCount benchmarks the Count method of Sketch. +func BenchmarkSketchCount(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + for _, windowSize := range windowSizes { + for _, historyLen := range []int{windowSize / 2, windowSize} { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d_WindowSize=%d_HistoryLen=%d", k, depth, width, windowSize, historyLen), func(b *testing.B) { + sketch := sliding.New(k, windowSize, + sliding.WithDepth(depth), + sliding.WithWidth(width), + sliding.WithBucketHistoryLength(historyLen), + ) + for _, item := range items { + sketch.Add(item, uint32(rand.IntN(10))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Count(items[rand.IntN(len(items))]) + } + }) + } + } + } + } + } +} + +// BenchmarkSketchQuery benchmarks the Query method of Sketch. +func BenchmarkSketchQuery(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + for _, windowSize := range windowSizes { + for _, historyLen := range []int{windowSize / 2, windowSize} { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d_WindowSize=%d_HistoryLen=%d", k, depth, width, windowSize, historyLen), func(b *testing.B) { + sketch := sliding.New(k, windowSize, + sliding.WithDepth(depth), + sliding.WithWidth(width), + sliding.WithBucketHistoryLength(historyLen), + ) + for _, item := range items { + sketch.Add(item, uint32(rand.IntN(10))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Query(items[rand.IntN(len(items))]) + } + }) + } + } + } + } + } +} + +// BenchmarkSketchTick benchmarks the Tick method of Sketch. +func BenchmarkSketchTick(b *testing.B) { + for _, k := range ks { + for _, depth := range depths { + for _, width := range widths { + for _, windowSize := range windowSizes { + for _, historyLen := range []int{windowSize / 2, windowSize} { + b.Run(fmt.Sprintf("K=%d_Depth=%d_Width=%d_WindowSize=%d_HistoryLen=%d", k, depth, width, windowSize, historyLen), func(b *testing.B) { + sketch := sliding.New(k, windowSize, + sliding.WithDepth(depth), + sliding.WithWidth(width), + sliding.WithBucketHistoryLength(historyLen), + ) + + for _, item := range items { + sketch.Add(item, uint32(rand.IntN(10))) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sketch.Tick() + } + }) + } + } + } + } + } +}