Skip to content

[proxy/json]: optimisations for string escaping#12643

Open
conradludgate wants to merge 9 commits intomainfrom
conrad/json-string-optimisations
Open

[proxy/json]: optimisations for string escaping#12643
conradludgate wants to merge 9 commits intomainfrom
conrad/json-string-optimisations

Conversation

@conradludgate
Copy link
Copy Markdown
Contributor

@conradludgate conradludgate commented Jul 17, 2025

I spent some time optimising the string routines. Much of the time in json encoding is spent on strings. We can skip a bunch of that with keys that are statically known to not need escaping. I've also spent some effort optimising the assembly of the string escaping. It's very marginal gains but it's gains nonetheless.

Before:

small                   time:   [6.3211 ns 6.3785 ns 6.4460 ns]

large_fmt               time:   [206.45 ns 208.20 ns 209.96 ns]

After:

small                   time:   [5.3861 ns 5.4242 ns 5.4664 ns]
                        change: [-14.858% -14.163% -13.512%] (p = 0.00 < 0.05)

small_static            time:   [2.9270 ns 2.9369 ns 2.9481 ns]

large_fmt               time:   [194.09 ns 196.21 ns 198.54 ns]
                        change: [-6.8999% -6.1206% -5.2425%] (p = 0.00 < 0.05)

Frankly, these micro-optimisations are unnecessary but they make me happy 😅

some statistics graphs

Small string

Screenshot 2025-07-18 at 09 32 11

Large string with fmt

Screenshot 2025-07-18 at 09 32 01

Small strings, const escaped vs dynamic escaped.

Screenshot 2025-07-18 at 09 34 43
comparing the assembly for the hot loops

Before

https://godbolt.org/z/vsYqTzv4r

.LBB2_5:
        xor     r13d, r13d
.LBB2_6:
        lea     rax, [r14 + r13]
        cmp     rax, rbp
        je      .LBB2_11
        movzx   ebx, byte ptr [rax]
        movzx   r15d, byte ptr [rbx + rdx]
        inc     r13
        test    r15d, r15d
        je      .LBB2_6 ; MAIN LOOP, 8 INSTRUCTIONS
        lea     rsi, [rcx + r13]
        dec     rsi
        cmp     rsi, rcx
        jb      .LBB2_13
        cmp     rsi, r9
        ja      .LBB2_10
        lea     rdx, [r13 - 1]
        mov     rax, qword ptr [rdi]
        sub     rax, r12
        cmp     rdx, rax
        mov     qword ptr [rsp + 24], r8
        ja      .LBB2_15
.LBB2_16:
        ; 88 lines of inlined write_char_escape ommited for brevity
        jmp     .LBB2_5

After

https://godbolt.org/z/dsbz7M6WP

.LBB2_5:
        xor     edx, edx
.LBB2_6:
        cmp     r14, rdx
        je      .LBB2_7
        movzx   eax, byte ptr [rsi + rdx]
        inc     rdx
        cmp     byte ptr [rax + r15], 0
        je      .LBB2_6 ; MAIN LOOP, 6 INSTRUCTIONS
        lea     r12, [rsi + rdx]
        sub     r14, rdx
        mov     rdi, rbx
        call    example::write_char_escape::hc44aa4c48ff73fd1
        mov     rsi, r12
        jmp     .LBB2_5

I'm gonna attempt to push some of these changes back into serde_json as well.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Jul 17, 2025

8855 tests run: 8192 passed, 0 failed, 663 skipped (full report)


Flaky tests (2)

Postgres 17

Postgres 14

Code coverage* (full report)

  • functions: 35.1% (8837 of 25151 functions)
  • lines: 46.4% (71122 of 153313 lines)

* collected from Rust tests only


The comment gets automatically updated with the latest test results
ce6bbca at 2025-07-18T10:35:03.965Z :recycle:

@conradludgate conradludgate force-pushed the conrad/json-string-optimisations branch 2 times, most recently from e27a495 to 46d0563 Compare July 18, 2025 08:37
@conradludgate conradludgate force-pushed the conrad/json-string-optimisations branch from 46d0563 to a681359 Compare July 18, 2025 08:52
…we don't need to roundtrip via the CharEscape enum.

We also don't need to return the 'rest' slice, we can just insert it into the vec

Lastly, we can simplify the hotloop by making write_char_escape cold and moving the vec write inside this fn
… use bytes[..i]. this ends up optimising better
@conradludgate conradludgate force-pushed the conrad/json-string-optimisations branch from a681359 to ce6bbca Compare July 18, 2025 09:00
@conradludgate conradludgate marked this pull request as ready for review July 18, 2025 14:43
@conradludgate conradludgate requested review from a team as code owners July 18, 2025 14:43
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