Skip to content

perf(device-registry): build lookup keys with CompactString (inline, no heap) - #682

Merged
jlucaso1 merged 1 commit into
mainfrom
perf/compactstring-lookup-keys
Jun 1, 2026
Merged

perf(device-registry): build lookup keys with CompactString (inline, no heap)#682
jlucaso1 merged 1 commit into
mainfrom
perf/compactstring-lookup-keys

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Context

Continuing the large-group send profiling (after #677/#680/#681). With the redundant key re-clone gone (#681), the next dhat hotspot in get_user_devices (per-member device resolution, run on every group send) was resolve_lookup_keys building UserLookupKeys from std::String.

Finding

resolve_lookup_keys did user.to_string() for the user-derived lookup key — a heap allocation per member per send, even though LID/PN user parts are short (≤~18 chars). UserLookupKeys used String fields throughout.

Change

Switch UserLookupKeys to wacore_binary::CompactString (already the type of Jid.user). Short strings live inline, so the user-derived key no longer heap-allocates; the cache-derived key moves in via the same type. all_keys() still returns Vec<&str> (CompactString derefs to str), so every consumer is unchanged.

Measurement (dhat A/B, group-send, 800 members, 12 msgs)

metric #681 this PR Δ
get_user_devices allocs 22996 12538 −45%
get_user_devices bytes 2.44 MB 2.11 MB −0.33 MB
total run blocks 117245 105762 −10%
total run bytes 16.08 MB 15.91 MB −0.17 MB

Combined with #681, per-member device resolution went from 50915 → 12538 allocs (−75%).

Behavior is identical (same keys, same lookup order, same results) — allocation/CPU-churn only, on the per-member-per-send path that scales with group size × message rate.

Note on diminishing returns

This path is now close to its floor: the remaining churn is largely inherent (the actual Vec<Jid> device data returned per member, and moka cache machinery for the device-registry and LID-PN caches). The bigger structural lever — caching a group's fully-resolved device set to make warm sends O(1) instead of O(members) — is not pursued: per-send client CPU is already low (~0.15 s for 40 sends to 800 members), the latency is server/network fanout-bound, and it matches WA Web's per-send O(N) iteration.

Tests: clippy --all-targets -D warnings clean; whatsapp-rust lib suite (660) + device_registry (36) green.

…no heap)

resolve_lookup_keys built UserLookupKeys from std::String, so the user-derived
key (user.to_string()) heap-allocated per member on every group send even though
LID/PN user parts are short (<=~18 chars). Switch UserLookupKeys to
wacore_binary::CompactString (already the type of Jid.user): the user-derived key
is now stored inline (no heap), and the cache-derived key moves in via the same
type.

dhat A/B (group-send, 800 members, 12 msgs), on top of #681:
  get_user_devices allocs  22996 -> 12538  (-45%; -75% vs pre-#681's 50915)
  total run               117245 -> 105762 blocks (-10%), 16.08 -> 15.91 MB
all_keys() still yields &str (CompactString derefs), so consumers are unchanged.
Behavior identical; allocation/CPU-churn only on the per-member-per-send path.
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e0f8527a-ae3d-4a1a-aa62-8ca626dde300

📥 Commits

Reviewing files that changed from the base of the PR and between af1202e and 86f7b3c.

📒 Files selected for processing (1)
  • src/client/device_registry.rs

📝 Walkthrough

Summary by CodeRabbit

  • Refactor
    • Optimized internal data handling for improved efficiency.

Note: This release contains internal improvements with no visible changes to end-user functionality.

Walkthrough

This PR optimizes user identifier storage in the device registry by migrating UserLookupKeys fields from heap-allocated String to CompactString. The resolution logic is updated to construct enum variants using into() conversions, eliminating unnecessary string allocations while maintaining identical lookup semantics.

Changes

User lookup key storage optimization

Layer / File(s) Summary
CompactString migration for user lookup keys
src/client/device_registry.rs
UserLookupKeys enum variants changed to store lid, pn, and user as wacore_binary::CompactString instead of String. The resolve_lookup_keys function updated to construct variants via into() conversions from cached lookup results and input &str, eliminating prior to_string() allocations. Lookup decision flow (LID-first, then pn, then unknown) remains unchanged.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • oxidezap/whatsapp-rust#681: Related modification to device_registry.rs involving UserLookupKeys and how lookup keys are produced/consumed in the registry retrieval path.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: using CompactString instead of String for lookup keys to enable inline storage and eliminate heap allocations in performance-critical code.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, providing context on the performance profiling work, the specific problem addressed, the solution implemented, and detailed measurement results.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/compactstring-lookup-keys

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

Benchmark Results

67 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 3,933 3,933 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 12,038 12,038 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 43,514 43,514 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 19,365 19,365 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 68,579 68,579 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 76,679 76,679 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 2,230 2,230 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 5,988 5,988 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 183,371 183,396 -0.0%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 2,204,889 2,204,889 +0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 933,191 933,100 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 1,211,671 1,211,771 -0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,733,163 2,733,167 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 1,143,750 1,143,762 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 2,180,737 2,187,714 -0.3%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 7,635,994 7,637,410 -0.0%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 12,545,846 12,651,339 -0.8%
binary_benchmark::marshal_group::bench_marshal_allocating 71,296 71,296 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 71,349 71,349 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 98,416 98,416 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 78,796 78,796 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 71,396 71,396 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 7,599 7,599 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 7,642 7,642 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 9,354 9,354 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 530,581 530,581 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 530,149 530,149 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 531,504 531,504 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 8,506,104 8,506,104 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 8,450,452 8,450,452 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 19,677,941 19,677,941 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 2,468 2,468 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 33,558 33,558 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 787 787 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 526,830 526,830 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 4,990 4,990 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 5,315 5,315 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 61,874 61,874 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 5,347 5,347 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 61,942 61,942 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 6,734 6,734 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 85,585 85,585 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 477,570 477,570 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 11,563 11,563 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u32 396 396 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u32 120 120 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u64 439 439 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u64 153 153 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_i64 499 499 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_i64 162 162 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_loop_100_u64 44,624 44,624 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_loop_100_u64 16,424 16,424 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 17,299,591 17,145,024 +0.9%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 157,179 157,179 +0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 5,513,975 5,513,975 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 157,539 157,539 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 296,767 296,767 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 706,282 706,282 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 12,498,331 12,391,706 +0.9%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 27,600,386 27,450,231 +0.5%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 3,467,011 3,467,011 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 125,490,083 125,434,893 +0.0%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,830,452 2,830,452 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 46,566 46,566 +0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 5,197,012 5,197,012 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 360,648 360,648 +0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 14,255,917 14,255,917 +0.0%
No significant changes detected.

@jlucaso1
jlucaso1 merged commit 856170a into main Jun 1, 2026
11 checks passed
@jlucaso1
jlucaso1 deleted the perf/compactstring-lookup-keys branch June 1, 2026 20:51
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