Skip to content

perf: replace O(n²) String.append loops with StringBuf#1

Merged
hellerve merged 1 commit into
mainfrom
claude/strbuf-perf
Jun 8, 2026
Merged

perf: replace O(n²) String.append loops with StringBuf#1
hellerve merged 1 commit into
mainfrom
claude/strbuf-perf

Conversation

@carpentry-agent

Copy link
Copy Markdown

Summary

join-children, join-segments, and escape-string in Form all built strings using set! out (String.append &out ...) inside loops. Each String.append allocates a new string and copies the accumulated content, making these O(n²) in the output length. For large ASTs or long strings (e.g. formatter/linter processing big files), this causes quadratic allocation.

This PR replaces all three loops with StringBuf from strbuf@0.1.0, which grows geometrically and gives amortised O(1) appends — making the overall functions O(n).

Changes

  • Add strbuf@0.1.0 dependency
  • join-children: StringBuf.create + append-str per child, to-string at end
  • join-segments: same pattern with . separator
  • escape-string: same pattern, byte-walking logic unchanged

No behavioural changes — all existing round-trip tests pass identically.

Test plan

  • Existing test suite (test/carp-reader.carp) passes — all round-trip assertions exercise Form.str which calls these three functions
  • Verify no regressions in carp-fmt / angler on this file

Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.

join-children, join-segments, and escape-string built strings
byte-by-byte using String.append, causing quadratic allocation
for large ASTs and strings. Switch to StringBuf for amortised
O(n) appends. Adds strbuf@0.1.0 dependency.

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build & Tests

Build: pass (ARM Linux, local).
Tests: all 50 pass — round-trip assertions for integers, floats, strings, symbols, arrays, dicts, reader macros, comments, positions, and edge cases all green.
CI: Both macOS and Ubuntu pass (tests, lint, format check, docs).

Findings

1. Correctness: all three replacements are clean (no issues)

Traced through each function:

  • join-children: StringBuf.create + loop with append-str for space separator and child Form.str. Semantically identical to the old String.append loop. The when (Int.> i 0) guard correctly skips the leading separator.
  • join-segments: Same pattern with . separator. Identical semantics.
  • escape-string: The while-do loop with CRLF detection is preserved exactly. StringBuf.append-str replaces set! out (String.append ...) in both branches. The byte-walking logic and escape-string-byte calls are untouched.

2. StringBuf API usage is correct

StringBuf.append-str takes (&StringBuf, &String) and mutates in-place. StringBuf.to-string &sb returns an owned String and resets the buffer; sb is then dropped at scope end. No ownership issues.

3. CHANGELOG updated

The Unreleased section correctly documents the change and the new strbuf@0.1.0 dependency.

4. No edge cases found

Empty inputs (empty child list, empty segment list, empty string) all work correctly — the loops simply don't execute, and StringBuf.to-string on a fresh buffer returns "".

Verdict: merge

Mechanical O(n²) → O(n) replacement. Correct, well-tested, CHANGELOG updated. No issues found.

@hellerve hellerve merged commit 267bc57 into main Jun 8, 2026
2 checks passed
@hellerve hellerve deleted the claude/strbuf-perf branch June 8, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant