mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
d13612fdc9
* topdown: fix BenchmarkFunctionArgumentCounts query The benchmark was using `test.f(...)` syntax which OPA interprets as a built-in function call, causing "unsupported built-in" errors. Rules from compiled modules must be referenced via the `data` prefix. Changed query from `test.f(...)` to `data.test.f(...)` to properly reference the rule defined in the test package. Signed-off-by: alex60217101990 <alex6021710@gmail.com> * topdown: remove warmup stunts from benchmarks Go's benchmark framework automatically handles warmup iterations through its internal iteration count management. Removed: - Explicit warmup loops - Manual GC() and ReadMemStats() calls - Custom b.StopTimer() usage - Hand-calculated allocPerOp and mallocsPerOp metrics - b.ReportMetric() calls for standard metrics Signed-off-by: alex60217101990 <alex6021710@gmail.com> --------- Signed-off-by: alex60217101990 <alex6021710@gmail.com>
153 lines
3.8 KiB
Go
153 lines
3.8 KiB
Go
// Copyright 2026 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package topdown
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/open-policy-agent/opa/v1/ast"
|
|
"github.com/open-policy-agent/opa/v1/storage"
|
|
inmem "github.com/open-policy-agent/opa/v1/storage/inmem/test"
|
|
)
|
|
|
|
// BenchmarkBindingsAllocation benchmarks memory allocation for bindings with different sizes.
|
|
// This directly tests the optimization from issue #7266.
|
|
func BenchmarkBindingsAllocation(b *testing.B) {
|
|
tests := []struct {
|
|
name string
|
|
bindings int
|
|
}{
|
|
{"1_binding", 1},
|
|
{"2_bindings", 2},
|
|
{"3_bindings", 3},
|
|
{"5_bindings", 5},
|
|
{"10_bindings", 10},
|
|
{"16_bindings", 16},
|
|
{"20_bindings", 20},
|
|
{"50_bindings", 50},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
b.Run(tt.name+"_without_hint", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
bi := newBindings(0, nil)
|
|
for j := range tt.bindings {
|
|
key := ast.VarTerm(fmt.Sprintf("x%d", j))
|
|
val := ast.IntNumberTerm(j)
|
|
bi.bind(key, val, nil, &undo{})
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run(tt.name+"_with_hint", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
bi := newBindingsWithSize(0, nil, tt.bindings)
|
|
for j := range tt.bindings {
|
|
key := ast.VarTerm(fmt.Sprintf("x%d", j))
|
|
val := ast.IntNumberTerm(j)
|
|
bi.bind(key, val, nil, &undo{})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkFunctionArgumentCounts benchmarks functions with varying argument counts.
|
|
// This demonstrates the memory waste when small functions allocate 16-slot arrays.
|
|
func BenchmarkFunctionArgumentCounts(b *testing.B) {
|
|
argCounts := []int{1, 2, 3, 5, 10, 15, 20}
|
|
|
|
for _, argCount := range argCounts {
|
|
b.Run(fmt.Sprintf("%d_args", argCount), func(b *testing.B) {
|
|
ctx := b.Context()
|
|
|
|
// Create function with N arguments
|
|
args := make([]string, argCount)
|
|
checks := make([]string, argCount)
|
|
for i := range argCount {
|
|
args[i] = fmt.Sprintf("x%d", i)
|
|
checks[i] = fmt.Sprintf("%s == %d", args[i], i)
|
|
}
|
|
|
|
module := fmt.Sprintf(`package test
|
|
|
|
f(%s) if {
|
|
%s
|
|
}
|
|
`, strings.Join(args, ", "), strings.Join(checks, "\n\t\t\t\t"))
|
|
|
|
compiler := ast.MustCompileModules(map[string]string{
|
|
"test.rego": module,
|
|
})
|
|
|
|
store := inmem.NewFromObject(map[string]any{})
|
|
|
|
// Create call with matching arguments
|
|
callArgs := make([]string, argCount)
|
|
for i := range argCount {
|
|
callArgs[i] = strconv.Itoa(i)
|
|
}
|
|
query := ast.MustParseBody(fmt.Sprintf(`data.test.f(%s)`, strings.Join(callArgs, ", ")))
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
for b.Loop() {
|
|
err := storage.Txn(ctx, store, storage.TransactionParams{}, func(txn storage.Transaction) error {
|
|
q := NewQuery(query).
|
|
WithCompiler(compiler).
|
|
WithStore(store).
|
|
WithTransaction(txn)
|
|
|
|
_, err := q.Run(ctx)
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkBindingsArrayHashmapTransition benchmarks the transition from array to map mode.
|
|
func BenchmarkBindingsArrayHashmapTransition(b *testing.B) {
|
|
b.Run("without_hint_transition_at_17", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
bh := newBindingsArrayHashmap()
|
|
// Add 17 bindings to force transition to map
|
|
for j := range 17 {
|
|
key := ast.VarTerm(fmt.Sprintf("x%d", j))
|
|
val := value{v: ast.IntNumberTerm(j)}
|
|
bh.Put(key, val)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("with_hint_starts_with_map", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
bh := newBindingsArrayHashmapWithSize(17)
|
|
// Add 17 bindings directly to map (no transition)
|
|
for j := range 17 {
|
|
key := ast.VarTerm(fmt.Sprintf("x%d", j))
|
|
val := value{v: ast.IntNumberTerm(j)}
|
|
bh.Put(key, val)
|
|
}
|
|
}
|
|
})
|
|
}
|