Repository navigation
Build long strings straight into their value: one allocation each (0151) - #469
Merged
Merged
Conversation
…ng (0151) A long string value now keeps its characters in its own StringObject, right after the object in one allocation, instead of an object holding a std::string. Visitors see such text as a view of the object; IntValue2Value copies it into the Value. Producers write the text straight into the value: InternalValue::MakeText and FromText, and StringBuilder (fmt::basic_memory_buffer) for text of unknown length. Converted: join (no longer quadratic), escape/forceescape, ~ on two strings, + and * on strings, slices and indexing, trim, title, upper/lower/capitalize, truncate, tojson, Value strings taken by value, and string literals at load. join of an empty sequence is now an empty string, as in Python (was undefined). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8ZupekfuoV2DNHDYyPVmG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8ZupekfuoV2DNHDYyPVmG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8ZupekfuoV2DNHDYyPVmG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8ZupekfuoV2DNHDYyPVmG
CodeQL's suspicious-add-sizeof query flagged sizeof added to a std::byte pointer; an unsigned char pointer is the unscaled byte arithmetic it expects. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8ZupekfuoV2DNHDYyPVmG
rmorozov
marked this pull request as ready for review
October 11, 2026 21:57
rmorozov
pushed a commit
that referenced
this pull request
Oct 11, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KMcWcpGwhiaEs33YjAgEV4
rmorozov
added a commit
that referenced
this pull request
Oct 11, 2026
Claude-Session: https://claude.ai/code/session_01KMcWcpGwhiaEs33YjAgEV4 Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Ruslan · project thread
Before: since 0140 P2 (#462) a string longer than 15 characters lived in a refcounted
StringObjectthat held astd::string, so every long string a render made cost two allocations (the string's buffer, then the object), andjoinrebuilt its result once per item. Render allocations on string-heavy cases rose 14-32% (targets in docs/tasks/0151).After: a long string's characters follow its
StringObjectin one allocation, and the code that makes strings writes them there once. All 0151 targets are met, and Render instructions drop on every string-heavy case.joinof an empty sequence is now'', as in Python (it was undefined; parity casefilters.join_empty).How.
StringObject::Make(size)allocates the object plus its characters (classoperator new(size_t, CharCount), matchingoperator deletes). Producers useInternalValue::MakeText(size, writer)(writer gets aboost::span<CharT>),InternalValue::FromText(views...), orStringBuilder(newsrc/string_builder.h,fmt::basic_memory_bufferwith 500 characters on the stack) when the length is unknown. Converted:join,escape/forceescape,~on two strings,+/*on strings, slices and indexing,trim,title,upper/lower/capitalize,truncate,tojson,Valuestrings taken by value, string literals at load (escaped ones on the stack intemplate_parser.cpp). Visitors now get a heap string as astd::basic_string_viewof the object (short strings are still materialised asstd::basic_string);IntValue2Valuecopies owned text into theValue, so a user callable can keep its arguments (testCallableKeepsBuiltStringArguments).TakeStringalways copies now.Two layout-only tweaks hold instruction counts at base:
ApplyNewlineSequenceout of line (it otherwise pullsMakeRawTextRendererout ofFineParseBlock, Load/large_static +0.8%) and~WorkspaceMapsforced inline (GCC outlined it inRender, Render/plain_text +5 Ir).Local counts (count.py, ce547f1 → this branch; allocations per iteration)
No benchmark above base by more than +0.05% (Load/for_filter_if). Performance track verdict on CI job 114577037925: approved, no red gate cases.
Left at two allocations (none shows in the suite):
indent,urlize,center,wordwrap,striptags,replace,format,~with a non-string operand. Listed in docs/tasks/0151.Roles: main session; verifier 1 round (ASan+UBSan, clang C++20, ~10k differential templates vs base and Python: no behaviour change except the empty join; its blocking items were clang-tidy hits, fixed before the first push). Red pushes: 1 (461ba2d: CodeQL
suspicious-add-sizeofon the characters' offset, added to astd::bytepointer; 8fe177a counts it on anunsigned charpointer).🤖 Generated with Claude Code
https://claude.ai/code/session_01G8ZupekfuoV2DNHDYyPVmG
Generated by Claude Code