![]() |
LambLisp 01 Red Fox Alpha
From AI to Actuator
|
Public Member Functions | |
| LL_String (const char *other) | |
| LL_String (const char *other, int n) | |
| LL_String (const LL_String &other) | |
| void | set (const char *s) |
| void | set (const char *s, int n) |
| LL_String | concat (const char *s1, const char *s2) |
| LL_String & | append (const char *s1) |
| LL_String & | append (char c) |
| LL_String | substring (int start, int len_) |
| LL_String & | operator= (const char *other) |
| LL_String & | operator= (const LL_String &other) |
| bool | operator== (const char *other) |
| bool | operator!= (const char *other) |
| bool | operator== (const LL_String &other) |
| bool | operator!= (const LL_String &other) |
| char & | operator[] (int ix) |
| LL_String | operator+ (const char *other) |
| LL_String | operator+ (const LL_String &other) |
| LL_String | operator+ (char c) |
| LL_String & | operator+= (const char *other) |
| LL_String & | operator+= (const LL_String &other) |
| LL_String & | operator+= (char c) |
| char * | c_str () const |
| int | length () const |
A small owning string. [B834] AMORTISED GROWTH – it used to reallocate and full-copy on EVERY append, which made the REPL's per-character input accumulation quadratic.
WHAT IT COST, measured before the change. ll_vm_term.cpp's commandLine += c runs once per RECEIVED CHARACTER, and append did strlen + new char[n0+n1+1] + copy-all + delete[] – so every byte of an input line cost a heap allocation and a full copy, O(n^2) per line. A 150-byte LLIP frame meant ~150 allocations and ~11,000 byte copies on a 240 MHz part. That is the ~100 B/s ceiling _w3_llip_stage_tests derives its timeouts from (r7rs-tests.scm: 904.7 s for 91,848 B) on a line whose wire rate is ~11.5 KB/s – under 1% of the link. push_file's comment named the symptom ("O(n) String rebuild per char") without naming this as the cause.
NOW: a stored length and capacity, geometric growth, in-place append. += is amortised O(1) and length() is O(1) instead of a strlen.
MEASURED, same benchmark against both classes (10 correctness arms + a growth curve): 20,000 single-char appends old 201 ms new 0.13 ms ~1,500x 10x the work old 41x time new 13x time (quadratic -> near-linear) The old timing was first measured as 0.17 ms, which is 8.5 ns per append and impossible for a heap allocation plus a full copy – the harness had compiled the NEW class twice, because a quoted #include searches the including file's own directory first. An impossible number is the only reason that was caught, so the arm now verifies which class it built before trusting the figure.
THREE THINGS THAT MADE THIS NOT A ONE-LINE CHANGE, each checked rather than assumed:
A NOTE ON WHAT GOT SAFER RATHER THAN RISKIER. c_str() hands out ptr, and a caller caching it across a mutation was ALWAYS reading freed memory before (the buffer was replaced every time); now it survives every append that does not grow. That is strictly fewer dangling reads, not more – but it also means such a misuse would now be intermittent rather than constant, so do not cache c_str() across a mutation.
|
inline |
< aliases us: no growth can be needed
|
inline |
< may move ptr; preserves [0,len]
|
inline |
Appending ONE character is the hot path (the terminal's per-char input accumulation), so it does not route through the 2-byte-cstr + strlen detour the operator used to take.
|
inline |
O(1) since [B834]; was a strlen on every call.