From b472c8de30e13b0503acbe0624e6f4d31bd540df Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Fri, 3 Apr 2026 13:11:59 -0700 Subject: [PATCH] =?UTF-8?q?perf:=20replace=20O(n=C2=B2)=20selection=20sort?= =?UTF-8?q?=20with=20sort.Slice=20(#354)=20(#542)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #354 Replaces the O(n²) selection sort in `sortedCopy()` with Go's built-in `sort.Float64s()` (O(n log n)). ## Changes - **`cmd/server/routes.go`**: Replaced manual nested-loop selection sort with `sort.Float64s(cp)` - **`cmd/server/helpers_test.go`**: Added regression test with 1000-element random input + benchmark ## Benchmark Results (ARM64) ``` BenchmarkSortedCopy/n=256 ~16μs/op 1 alloc BenchmarkSortedCopy/n=1000 ~95μs/op 1 alloc BenchmarkSortedCopy/n=10000 ~1.3ms/op 1 alloc ``` With the old O(n²) sort, n=10000 would take ~50ms+. The new implementation scales as O(n log n). ## Testing - All existing `TestSortedCopy` tests pass (unchanged behavior) - New `TestSortedCopyLarge` validates correctness on 1000 random elements - `go test ./...` passes in `cmd/server` Co-authored-by: you --- cmd/server/helpers_test.go | 40 ++++++++++++++++++++++++++++++++++++++ cmd/server/routes.go | 8 +------- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/cmd/server/helpers_test.go b/cmd/server/helpers_test.go index 7ec5c703..b2bff47a 100644 --- a/cmd/server/helpers_test.go +++ b/cmd/server/helpers_test.go @@ -2,6 +2,8 @@ package main import ( "encoding/json" + "fmt" + "math/rand" "net/http" "net/http/httptest" "os" @@ -220,6 +222,44 @@ func TestSortedCopy(t *testing.T) { } } +func TestSortedCopyLarge(t *testing.T) { + // Regression: verify correct sort on larger input + rng := rand.New(rand.NewSource(42)) + n := 1000 + input := make([]float64, n) + for i := range input { + input[i] = rng.Float64() * 1000 + } + result := sortedCopy(input) + if len(result) != n { + t.Fatalf("expected %d elements, got %d", n, len(result)) + } + for i := 1; i < len(result); i++ { + if result[i] < result[i-1] { + t.Fatalf("not sorted at index %d: %v > %v", i, result[i-1], result[i]) + } + } + // Original unchanged + if input[0] == result[0] && input[1] == result[1] && input[2] == result[2] { + // Could be coincidence but very unlikely with random data + } +} + +func BenchmarkSortedCopy(b *testing.B) { + rng := rand.New(rand.NewSource(42)) + for _, size := range []int{256, 1000, 10000} { + data := make([]float64, size) + for i := range data { + data[i] = rng.Float64() * 1000 + } + b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) { + for i := 0; i < b.N; i++ { + sortedCopy(data) + } + }) + } +} + func TestLastN(t *testing.T) { arr := []map[string]interface{}{ {"id": 1}, {"id": 2}, {"id": 3}, {"id": 4}, {"id": 5}, diff --git a/cmd/server/routes.go b/cmd/server/routes.go index 87c9a5fd..98bef917 100644 --- a/cmd/server/routes.go +++ b/cmd/server/routes.go @@ -1958,13 +1958,7 @@ func percentile(sorted []float64, p float64) float64 { func sortedCopy(arr []float64) []float64 { cp := make([]float64, len(arr)) copy(cp, arr) - for i := 0; i < len(cp); i++ { - for j := i + 1; j < len(cp); j++ { - if cp[j] < cp[i] { - cp[i], cp[j] = cp[j], cp[i] - } - } - } + sort.Float64s(cp) return cp }