diff --git a/.jules/bolt.md b/.jules/bolt.md index c214509..b38cf5d 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -21,3 +21,6 @@ ## 2025-05-10 - fmt.Sscanf is lenient, strconv.Atoi is strict **Learning:** In Go, `fmt.Sscanf("%d")` parses digits until it encounters a non-digit character (e.g., `"2beta"` parses as `2`), while `strconv.Atoi` fails and returns `0` for the entire string. If you need the lenient parsing behavior of `fmt.Sscanf` for performance optimization, implement a custom byte-traversal loop to extract leading digits rather than relying on `strconv.Atoi` or regex, as it is over 10x faster and maintains exact functional parity. **Action:** When replacing `fmt.Sscanf` for performance, always evaluate whether the leniency of the parser is being implicitly relied upon by the surrounding code. +## 2025-05-11 - Use strings.Cut instead of strings.Split in parsing loops +**Learning:** In Go, using `strings.Split` in tight parsing loops (like version string comparisons) causes unnecessary slice allocations and degrades performance. +**Action:** To avoid this overhead and improve speed, use a `for` loop with `strings.Cut` to incrementally consume and parse the delimited string without allocating intermediate slices. diff --git a/internal/plugin/manager.go b/internal/plugin/manager.go index 7aad027..fe192e1 100644 --- a/internal/plugin/manager.go +++ b/internal/plugin/manager.go @@ -832,24 +832,22 @@ func parseLenientAtoi(s string) int { // CompareVersions compares two semver strings // Returns -1 if a < b, 0 if a == b, 1 if a > b func CompareVersions(a, b string) int { - partsA := strings.Split(a, ".") - partsB := strings.Split(b, ".") + // Bolt optimization: using a for loop with strings.Cut avoids intermediate + // slice allocations entirely compared to strings.Split, improving performance. + for len(a) > 0 || len(b) > 0 { + var partA, partB string - maxLen := len(partsA) - if len(partsB) > maxLen { - maxLen = len(partsB) - } - - for i := 0; i < maxLen; i++ { - var numA, numB int - if i < len(partsA) { - // Bolt optimization: using a custom loop is ~14x faster than fmt.Sscanf - numA = parseLenientAtoi(partsA[i]) + if len(a) > 0 { + partA, a, _ = strings.Cut(a, ".") } - if i < len(partsB) { - numB = parseLenientAtoi(partsB[i]) + if len(b) > 0 { + partB, b, _ = strings.Cut(b, ".") } + // Bolt optimization: using a custom loop is ~14x faster than fmt.Sscanf + numA := parseLenientAtoi(partA) + numB := parseLenientAtoi(partB) + if numA < numB { return -1 } diff --git a/internal/plugins/update.go b/internal/plugins/update.go index cab62bd..8b904db 100644 --- a/internal/plugins/update.go +++ b/internal/plugins/update.go @@ -189,23 +189,21 @@ func saveUpdateCache(c updateCache) { // compareVersions compares two semver strings // Returns -1 if a < b, 0 if a == b, 1 if a > b func compareVersions(a, b string) int { - partsA := strings.Split(a, ".") - partsB := strings.Split(b, ".") + // Bolt optimization: using a for loop with strings.Cut avoids intermediate + // slice allocations entirely compared to strings.Split, improving performance. + for len(a) > 0 || len(b) > 0 { + var partA, partB string - maxLen := len(partsA) - if len(partsB) > maxLen { - maxLen = len(partsB) - } - - for i := 0; i < maxLen; i++ { - var numA, numB int - if i < len(partsA) { - numA, _ = strconv.Atoi(partsA[i]) + if len(a) > 0 { + partA, a, _ = strings.Cut(a, ".") } - if i < len(partsB) { - numB, _ = strconv.Atoi(partsB[i]) + if len(b) > 0 { + partB, b, _ = strings.Cut(b, ".") } + numA, _ := strconv.Atoi(partA) + numB, _ := strconv.Atoi(partB) + if numA < numB { return -1 } diff --git a/internal/update/update.go b/internal/update/update.go index 7bf6684..e3f2fda 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -157,23 +157,21 @@ func fetchLatestVersion(ctx context.Context) (string, error) { } func compareVersions(a, b string) int { - partsA := strings.Split(a, ".") - partsB := strings.Split(b, ".") + // Bolt optimization: using a for loop with strings.Cut avoids intermediate + // slice allocations entirely compared to strings.Split, improving performance. + for len(a) > 0 || len(b) > 0 { + var partA, partB string - maxLen := len(partsA) - if len(partsB) > maxLen { - maxLen = len(partsB) - } - - for i := 0; i < maxLen; i++ { - var numA, numB int - if i < len(partsA) { - numA, _ = strconv.Atoi(partsA[i]) + if len(a) > 0 { + partA, a, _ = strings.Cut(a, ".") } - if i < len(partsB) { - numB, _ = strconv.Atoi(partsB[i]) + if len(b) > 0 { + partB, b, _ = strings.Cut(b, ".") } + numA, _ := strconv.Atoi(partA) + numB, _ := strconv.Atoi(partB) + if numA < numB { return -1 }