Skip to content

perf: stop copying every query param value - #62

Merged
ShawnChen-Sirius merged 1 commit into
chdb-io:mainfrom
hyperbyte-cloud:perf/param-encoding-copies
Sep 30, 2026
Merged

ShawnChen-Sirius merged 1 commit into
chdb-io:mainfrom
hyperbyte-cloud:perf/param-encoding-copies

Conversation

@austin-barrington

@austin-barrington austin-barrington commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

QueryParam::encode returns a Cow so it can borrow when a value doesn't need escaping, but EncodedParams then called .to_vec() on every value regardless. That meant a large text param got copied even though we're consuming the QueryParams and already own the String.

Changes:

  • Params are consumed through a new private into_encoded(). Text that needs no escaping and Raw values are moved through as-is, no copy. Anything that does need escaping gets encoded like before.
  • Arrays are written straight into one String. Before, each element got its own String, then they were joined, then wrapped with format!.
  • The name/value buffers are sized up front.

Numbers from a quick timing loop (release, 20 runs each):

  • 100k element Array(UInt64): ~63ms → ~60ms
  • 8MB String: ~124ms → ~118ms
  • single small scalar: no change

Most of the time is the engine parsing the value, so it's not a huge win, but it drops a full copy of every value on the way in. The encoded output is identical, the existing encoding tests all pass unchanged, and there's a new test that checks plain text and raw values really come back as the same allocation.

Note

Remove per-element string allocation in query param encoding

Rewrites query param value encoding in query_param.rs to write directly into a single output buffer instead of allocating an intermediate String for each nested element and joining them.

  • QueryParam.encode_array and the new write_array helper build array literals (brackets, separators, elements) in place, reserving capacity from the element count.
  • write_nested and write_quoted replace the allocating encode_nested and quote_nested_string helpers; escaping rules for backslashes, tabs, newlines, and apostrophes are unchanged.
  • New QueryParam.into_encoded consumes the value so unescaped text and raw owned strings move into the encoded result without copying. EncodedParams.encode preallocates its buffer vectors from the collection length.
  • Adds a test asserting that owned unescaped text and raw values keep their original allocation, and that tab-containing text is escaped.
  • Behavioral Change: emitted parameter strings, lengths, and pointers exposed to the C FFI call are unchanged; only allocation behavior differs.

Macroscope summarized 6d75a1d.

QueryParam::encode already returns a Cow that borrows when nothing needs
escaping, but EncodedParams then called .to_vec() on every value anyway.
So a big text param was copied even though we own the String and could
just move it.

Params are now consumed with into_encoded(), which moves text that needs
no escaping and raw literals straight through. Arrays are written into a
single String instead of a String per element, then join, then format!.

About 5% faster on an 8MB text param and a 100k element array. Small
params are unchanged.
@ShawnChen-Sirius
ShawnChen-Sirius merged commit 069072d into chdb-io:main Sep 30, 2026
11 checks passed
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.

2 participants