Skip to content

feat: use HashMap for create_shared_string to fix O(N²) performance#8958

Open
statxc wants to merge 3 commits intogoogle:masterfrom
statxc:rust/optimize-create-shared-string-hashmap
Open

feat: use HashMap for create_shared_string to fix O(N²) performance#8958
statxc wants to merge 3 commits intogoogle:masterfrom
statxc:rust/optimize-create-shared-string-hashmap

Conversation

@statxc
Copy link

@statxc statxc commented Mar 6, 2026

Closes: #8687

Problem

FlatBufferBuilder::create_shared_string maintains a sorted Vec of offsets for string deduplication. On a cache miss, it inserts into the Vec using Vec::insert, which shifts all subsequent elements - an O(N) operation. Since every unique string triggers this path, inserting N unique strings costs O(N²) total.

With ~500K unique strings, this causes ~80% of serialization time to be spent in create_shared_string.

Fix

Replace the sorted Vec with a HashMap<String, WIPOffset<&'fbb str>> for the std feature (the default). Lookup and insertion are both O(1) amortized instead of O(log N) + O(N).

The original Vec-based implementation is preserved behind #[cfg(not(feature = "std"))] since alloc does not provide HashMap.

Changes

  • rust/flatbuffers/src/builder.rs - swap strings_pool from Vec to HashMap (cfg-gated), update new_in() and create_shared_string accordingly
  • tests/rust_usage_test/tests/integration_test.rs - add test_shared_strings_pool_deduplication covering uniqueness, deduplication, reset behavior, and end-to-end buffer correctness

Is this a breaking change?

No. strings_pool is a private field - no user code can reference it. The public signature of create_shared_string is identical. The only observable difference is faster performance. Both std and no_std builds compile cleanly, and all 264 existing tests pass unchanged.

Test added?

Yes. test_shared_strings_pool_deduplication verifies:

  • Unique strings get distinct offsets
  • Duplicate strings return the same offset
  • reset() clears the pool and deduplication still works afterward
  • Shared strings produce a valid, readable FlatBuffer with correct data in nested objects

Test plan

# Build with std (default)
cd rust/flatbuffers && cargo build

# Build with no_std
cd rust/flatbuffers && cargo build --no-default-features

# Run shared string tests
cd tests/rust_usage_test && cargo test test_shared_strings

# Run full test suite
cd tests/rust_usage_test && cargo test

@statxc statxc requested a review from dbaileychess as a code owner March 6, 2026 20:51
@google-cla
Copy link

google-cla bot commented Mar 6, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@github-actions github-actions bot added the rust label Mar 6, 2026
@statxc statxc force-pushed the rust/optimize-create-shared-string-hashmap branch from aead9fc to 77cdf32 Compare March 6, 2026 21:03
@statxc
Copy link
Author

statxc commented Mar 6, 2026

@jtdavis777 Could you please review this PR? thanks

@jtdavis777
Copy link
Collaborator

this will take me a bit to ingest, as I am a volunteer and not a rust specialist. But I really appreciate you working on the issue and will try to get this in as soon as I can. for now I can kick off the pipeline to make sure all works!

@jtdavis777
Copy link
Collaborator

hey @RenzoMXD -- would you be willing to help weigh in on some of these Rust PRs?

@jtdavis777
Copy link
Collaborator

I did a quick gpt session to understand what is going on here, and this seems like a safe and meaningful improvement. Having a test is perfect! GPT suggested a few small optimizations to improve efficiency overall -- feel free to take it with a grain of salt. I'd like another rust user to weigh in before I merge as well if possible just to make sure all is well.

let found = self.strings_pool.binary_search_by(|offset| {
    let ptr = offset.value() as usize;

    let str_memory = &buf[buf.len() - ptr..];

    let size = u32::from_le_bytes([
        str_memory[0],
        str_memory[1],
        str_memory[2],
        str_memory[3],
    ]) as usize;

    let stored = &str_memory[4..4 + size];

    stored.cmp(s.as_bytes())
});

@statxc
Copy link
Author

statxc commented Mar 7, 2026

I did a quick gpt session to understand what is going on here, and this seems like a safe and meaningful improvement. Having a test is perfect! GPT suggested a few small optimizations to improve efficiency overall -- feel free to take it with a grain of salt. I'd like another rust user to weigh in before I merge as well if possible just to make sure all is well.

let found = self.strings_pool.binary_search_by(|offset| {
    let ptr = offset.value() as usize;

    let str_memory = &buf[buf.len() - ptr..];

    let size = u32::from_le_bytes([
        str_memory[0],
        str_memory[1],
        str_memory[2],
        str_memory[3],
    ]) as usize;

    let stored = &str_memory[4..4 + size];

    stored.cmp(s.as_bytes())
});

This suggestion is good. The result is the same. Both compare stored string bytes against s in the same order. The suggestion is just shorter and slightly more efficient since slice comparison ([u8].cmp) can be optimized better by the compiler than iterator cloning.
I will update my solution following the suggestion.

@statxc statxc force-pushed the rust/optimize-create-shared-string-hashmap branch from c3876d5 to b0f7867 Compare March 7, 2026 04:47
@statxc
Copy link
Author

statxc commented Mar 7, 2026

@jtdavis777 I updated. please review again. Thanks

@RenzoMXD
Copy link
Contributor

RenzoMXD commented Mar 7, 2026

hey @RenzoMXD -- would you be willing to help weigh in on some of these Rust PRs?

LGTM. Code is clear and test works well. Thanks. @jtdavis777 @statxc

@statxc
Copy link
Author

statxc commented Mar 7, 2026

@jtdavis777 Any update for me, please

@jtdavis777
Copy link
Collaborator

Awesome, I will look this over again and get it merged as I have time today

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Rust] FlatBufferBuilder::create_shared_string is O(N^2)

3 participants