perf(tools): linear duplicate-key scan keyed on SimpleFold orbit
PR #66 review round 2: the pairwise EqualFold loop was quadratic, 7s of CPU for ~90k keys under the 1 MiB body cap. foldKey maps each rune to the smallest member of its unicode.SimpleFold orbit, equal exactly when strings.EqualFold is, so the scan is a map lookup again. The scaling test fails on the previous code (7.1s). Refs #65 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+25
-8
@@ -8,6 +8,7 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"git.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||
"git.d-ma.be/mathias/gitea-mcp/internal/registry"
|
||||
@@ -84,21 +85,18 @@ func scanJSONValue(dec *json.Decoder) error {
|
||||
}
|
||||
switch d {
|
||||
case '{':
|
||||
var seen []string
|
||||
seen := map[string]string{}
|
||||
for dec.More() {
|
||||
kt, err := dec.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k, _ := kt.(string)
|
||||
// EqualFold, not ToLower: encoding/json folds the same way, so
|
||||
// long s (U+017F) matches s and the Kelvin sign matches k.
|
||||
for _, prev := range seen {
|
||||
if strings.EqualFold(prev, k) {
|
||||
return fmt.Errorf("duplicate argument keys %q and %q (keys must be unique ignoring case): %w", prev, k, gitea.ErrValidation)
|
||||
}
|
||||
fk := foldKey(k)
|
||||
if prev, dup := seen[fk]; dup {
|
||||
return fmt.Errorf("duplicate argument keys %q and %q (keys must be unique ignoring case): %w", prev, k, gitea.ErrValidation)
|
||||
}
|
||||
seen = append(seen, k)
|
||||
seen[fk] = k
|
||||
if err := scanJSONValue(dec); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -114,6 +112,25 @@ func scanJSONValue(dec *json.Decoder) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// foldKey maps every rune to the smallest member of its unicode.SimpleFold
|
||||
// orbit, so foldKey(a) == foldKey(b) exactly when strings.EqualFold(a, b) —
|
||||
// the folding encoding/json uses (long s == s, Kelvin sign == k). A map keyed
|
||||
// on it keeps the duplicate scan linear (PR #66 review: a pairwise EqualFold
|
||||
// loop cost 7s of CPU on ~90k keys).
|
||||
func foldKey(s string) string {
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
min := r
|
||||
for f := unicode.SimpleFold(r); f != r; f = unicode.SimpleFold(f) {
|
||||
if f < min {
|
||||
min = f
|
||||
}
|
||||
}
|
||||
b.WriteRune(min)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// jsonFieldNames returns the json tag names of dst's top-level struct fields.
|
||||
func jsonFieldNames(dst any) []string {
|
||||
v := reflect.ValueOf(dst)
|
||||
|
||||
Reference in New Issue
Block a user