Skip to content

Speed up Slice::ToString(bool hex=true) - #15045

Open
thatsafunnyname wants to merge 11 commits into
facebook:mainfrom
thatsafunnyname:patch-16
Open

Speed up Slice::ToString(bool hex=true)#15045
thatsafunnyname wants to merge 11 commits into
facebook:mainfrom
thatsafunnyname:patch-16

Conversation

@thatsafunnyname

@thatsafunnyname thatsafunnyname commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
  • Replace branching with a single indexed load from constexpr lookup table.
  • Instead of reserve + push_back, one allocation sets final size immediately, avoiding repeated size updates and bounds checks.
  • Raw pointer skips repeated operator[] overhead and internal index tracking.
  • When hex=false, construct the string directly, avoiding assign + return copy.

Noticed when using ldb --value_hex scan:

Before:

perf stat -e branches,branch-misses tools/ldb --value_hex scan --db=test_db | dd of=/dev/null
 Performance counter stats for 'tools/ldb --value_hex scan --db=test_db':
     2,983,373,019      branches:u
       101,288,380      branch-misses:u           #    3.40% of all branches
       4.365280485 seconds time elapsed
       2.854659000 seconds user
       0.741348000 seconds sys
884520+1 records in
884520+1 records out
452874563 bytes (453 MB, 432 MiB) copied, 4.40603 s, 103 MB/s

After:

perf stat -e branches,branch-misses tools/ldb --value_hex scan --db=test_db | dd of=/dev/null
 Performance counter stats for 'tools/ldb --value_hex scan --db=test_db':
       275,719,553      branches:u
           797,887      branch-misses:u           #    0.29% of all branches
       1.922297831 seconds time elapsed
       0.346486000 seconds user
       0.785366000 seconds sys
884520+1 records in
884520+1 records out
452874563 bytes (453 MB, 432 MiB) copied, 1.97187 s, 230 MB/s

Built with DEBUG_LEVEL=0. gcc 8.5.0

Also see #15070 which incorporates the changes from this PR and also speeds up Slice::DecodeHex .

Replace branching with a single indexed load from constexpr lookup table.
Instead of reserve + push_back, one allocation sets final size immediately, avoiding repeated size updates and bounds checks.
Raw pointer skips repeated operator[] overhead and internal index tracking.
When hex=false, construct the string directly, avoiding assign + return copy.

Noticed when using "ldb --value_hex scan":

Before:

perf stat -e branches,branch-misses tools/ldb --value_hex scan --db=test_db | dd of=/dev/null
 Performance counter stats for 'tools/ldb --value_hex scan --db=test_db':
     2,983,373,019      branches:u
       101,288,380      branch-misses:u           #    3.40% of all branches
       4.365280485 seconds time elapsed
       2.854659000 seconds user
       0.741348000 seconds sys
884520+1 records in
884520+1 records out
452874563 bytes (453 MB, 432 MiB) copied, 4.40603 s, 103 MB/s

After:

perf stat -e branches,branch-misses tools/ldb --value_hex scan --db=test_db | dd of=/dev/null
 Performance counter stats for 'tools/ldb --value_hex scan --db=test_db':
       275,719,553      branches:u
           797,887      branch-misses:u           #    0.29% of all branches
       1.922297831 seconds time elapsed
       0.346486000 seconds user
       0.785366000 seconds sys
884520+1 records in
884520+1 records out
452874563 bytes (453 MB, 432 MiB) copied, 1.97187 s, 230 MB/s

Built with DEBUG_LEVEL=0. gcc 8.5.0
@meta-cla meta-cla Bot added the CLA Signed label Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

⚠️ clang-tidy: 2 warning(s) on changed lines

Completed in 981.9s.

Summary by check

Check Count
concurrency-mt-unsafe 1
performance-unnecessary-value-param 1
Total 2

Details

db_stress_tool/db_stress_test_base.cc (1 warning(s))
db_stress_tool/db_stress_test_base.cc:475:5: warning: function is not thread safe [concurrency-mt-unsafe]
utilities/backup/backup_engine.cc (1 warning(s))
utilities/backup/backup_engine.cc:2301:28: warning: parameter 'progress_callback' is passed by value and only copied once; consider moving it to avoid unnecessary copies [performance-unnecessary-value-param]

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Claude Code Review - OBSOLETE

Superseded by a newer AI review. Expand to see the original review.

✅ Claude Code Review

Auto-triggered after CI passed — reviewing commit 41d1c16


Summary

Clean, well-motivated performance optimization for Slice::ToString(hex=true) that replaces per-nibble branching with a lookup table and eliminates repeated push_back overhead. The approach is sound and the benchmark results are compelling. Two style issues noted; no correctness bugs found.

High-severity findings (0):

No high-severity findings.

Full review (click to expand)

Findings

🔴 HIGH

None.

🟡 MEDIUM

M1. Use lossless_cast instead of reinterpret_cast -- util/slice.cc:283
  • Issue: The new code uses reinterpret_cast<const unsigned char*>(data_). Per CLAUDE.md: "Avoid static_cast, reinterpret_cast, and C-style casts; static_cast_with_check, up_cast, and lossless_cast from cast_util.h are preferred."
  • Root cause: lossless_cast from util/cast_util.h supports exactly this pattern (char* <-> unsigned char* pointer conversion). The newer code in util/coding.h:109 already uses lossless_cast<const unsigned char*>(p) for the identical conversion. The older util/coding.cc:59 still has legacy reinterpret_cast for the same pattern, but new code should follow the modern convention.
  • Suggested fix: Replace:
    const unsigned char* src = reinterpret_cast<const unsigned char*>(data_);
    with:
    const unsigned char* src = lossless_cast<const unsigned char*>(data_);
    and add #include "util/cast_util.h" to the includes.

🟢 LOW / NIT

L1. Stale blank line after toHex removal -- util/slice.cc:241
  • Issue: After removing the toHex function, an extra blank line remains between the comment block and // most of the code is for validation/error check. This creates a visual gap that separates the comment header from the fromHex function it describes.
  • Suggested fix: Remove the extra blank line.
L2. No new tests added
  • Issue: There are currently zero tests for Slice::ToString() or DecodeHex() in util/slice_test.cc. While the change is behavior-preserving, a simple round-trip test would add confidence.
  • Suggested fix: Consider adding a test covering empty slice, known value ("\x00\xff" -> "00FF"), and round-trip encode/decode.
L3. Theoretical integer overflow in 2 * size_ -- util/slice.cc:280
  • Issue: 2 * size_ could overflow if size_ > SIZE_MAX/2. Pre-existing behavior (old code had same issue). Not actionable in practice.

Cross-Component Analysis

Context Assumptions hold? Action needed?
All platforms (C++20 required, string::data() mutable since C++17) YES None
Thread safety (const method, no shared mutable state) YES None
DecodeHex round-trip (output 0-9A-F, fromHex accepts both cases) YES None
Empty slice (size_==0, loop body never entered) YES None

Positive Observations

  • Lookup table eliminates per-nibble branching (10x branch reduction per benchmarks).
  • return {data_, size_} for non-hex path is cleaner than assign + return.
  • Pre-allocating exact size avoids repeated capacity updates from push_back.
  • static constexpr lookup table has zero runtime initialization cost.

ℹ️ About this response

Generated by Claude Code.
Review methodology: claude_md/code_review.md

Limitations:

  • Claude may miss context from files not in the diff
  • Large PRs may be truncated
  • Always apply human judgment to AI suggestions

Commands:

  • /claude-review [context] — Request a code review
  • /claude-query <question> — Ask about the PR or codebase

as per CLAUDE.md: "Avoid static_cast, reinterpret_cast, and C-style casts; static_cast_with_check, up_cast, and lossless_cast from cast_util.h are preferred."
that separates the comment header from the fromHex function it describes.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Claude Code Review - OBSOLETE

Superseded by a newer AI review. Expand to see the original review.

✅ Claude Code Review

Auto-triggered after CI passed — reviewing commit a947ff2


Summary

Clean, well-motivated performance optimization that replaces branching hex conversion with a lookup table and eliminates per-character push_back overhead. The benchmark shows a compelling ~2x speedup. The change is correct and preserves the existing behavioral contract.

High-severity findings (0):

No high-severity findings.

Full review (click to expand)

Findings

🔴 HIGH

None.

🟡 MEDIUM

M1. Include ordering — util/slice.cc
  • Issue: #include "util/cast_util.h" is added after "util/string_util.h". Alphabetically, cast_util.h should come before string_util.h. RocksDB uses sorted includes within each group and .clang-format enforces this.
  • Suggested fix: Move #include "util/cast_util.h" before #include "util/string_util.h", or run make format-auto to auto-fix.
M2. No test coverage for Slice::ToString(hex=true)util/slice_test.cc
  • Issue: There are no unit tests for Slice::ToString (hex or non-hex) or DecodeHex in slice_test.cc. While the behavioral contract is unchanged, a performance refactoring of encoding logic should ideally have a round-trip test to guard against regressions. At minimum, a test verifying ToString(true) output matches expected hex and round-trips through DecodeHex would be valuable.
  • Suggested fix: Add a test in slice_test.cc covering: empty slice, single byte, all 256 byte values, and round-trip with DecodeHex.

🟢 LOW / NIT

L1. Integer overflow in 2 * size_util/slice.cc:278
  • Issue: If size_ > SIZE_MAX / 2, 2 * size_ overflows. Pre-existing issue (old code had reserve(2 * size_)). Practically unreachable on 64-bit systems.
  • Suggested fix: No action needed.
L2. Comment update accuracy — util/slice.cc:241
  • Issue: Comment says "for efficient hex conversion" but fromHex is for decoding. Minor inaccuracy after removing toHex.
  • Suggested fix: Consider "for hex decoding" or leave as-is.
L3. Style: while vs for loop — util/slice.cc:283
  • Issue: while (src != end) with *src++ is fine but a for loop is slightly more conventional for range iteration. Both are equally clear.
  • Suggested fix: No action needed. Style preference only.

Cross-Component Analysis

Context Assumptions hold? Action needed?
WritePreparedTxnDB Yes Safe
ReadOnly DB Yes Safe
User-defined timestamps Yes Safe
Concurrent callers Yes (const, no shared mutable state) Safe
All platforms (MSVC, GCC, Clang) Yes (C++17 string::data()) Safe

Verified claims:

  • Lookup table output identical to toHex(): kHexChars[0..15] matches '0'+v / 'A'+v-10 for all nibble values. Correct.
  • return {data_, size_} equivalent to assign+return: Both construct std::string(const char*, size_t). New form leverages C++17 guaranteed copy elision. Correct.
  • toHex() has no other callers in codebase. Verified.
  • lossless_cast<const unsigned char*> has precedent at util/coding.h:109. Verified.

Positive Observations

  • ~2x speedup with minimal, clean code change
  • Properly uses lossless_cast per project guidelines
  • fromHex correctly preserved for DecodeHex
  • Non-hex fast path nicely leverages C++17

ℹ️ About this response

Generated by Claude Code.
Review methodology: claude_md/code_review.md

Limitations:

  • Claude may miss context from files not in the diff
  • Large PRs may be truncated
  • Always apply human judgment to AI suggestions

Commands:

  • /claude-review [context] — Request a code review
  • /claude-query <question> — Ask about the PR or codebase

Validates empty inputs, a single-byte value and a round-trip of all 256 possible byte values using both standard and hex representations.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Claude Code Review - OBSOLETE

Superseded by a newer AI review. Expand to see the original review.

✅ Claude Code Review

Auto-triggered after CI passed — reviewing commit 4cb05b8


Summary

Clean, well-motivated performance optimization with substantial measured improvement (2.3x throughput, 10x branch reduction). The implementation is correct and follows established codebase patterns.

High-severity findings (0):

No high-severity findings.

Full review (click to expand)

Findings

🔴 HIGH

None.

🟡 MEDIUM

M1. toHex had external linkage — fromHex still does — util/slice.cc:252

Both toHex (removed) and fromHex (kept) are defined at file scope without static or anonymous namespace. They have external linkage, which means they pollute the global symbol table. While removing toHex is safe (no external callers), fromHex remains exposed. This is a pre-existing issue, not introduced by this PR, but worth noting since the comment was updated.

  • Suggested fix: Not required for this PR, but consider wrapping fromHex in an anonymous namespace or adding static in a follow-up.
M2. Empty test fixture SliceHexTestutil/slice_test.cc:709

SliceHexTest derives from testing::Test but has no members, setup, or teardown. Per CLAUDE.md guidelines, TEST() should be preferred over TEST_F() when there's no fixture state. This also creates a named class that has no purpose.

  • Suggested fix: Use TEST(SliceHexTest, HexAndToStringRoundTrip) instead of TEST_F.

🟢 LOW / NIT

L1. Theoretical 2 * size_ overflow — util/slice.cc:280

If size_ > SIZE_MAX / 2, the expression 2 * size_ wraps around (unsigned overflow is well-defined), causing the std::string constructor to allocate a much smaller buffer than the loop writes into. On a 64-bit system, this requires a Slice referencing >8 EiB of memory, which is impossible. On a hypothetical 32-bit system, >2 GiB is theoretically achievable but extremely unlikely for a hex-encoding use case. The old code had the same issue (result.reserve(2 * size_) followed by 2 * size_ push_backs). This is not a regression.

  • Suggested fix: No action needed. If paranoia is desired, assert(size_ <= SIZE_MAX / 2) could be added.
L2. Round-trip test helper could be extracted — util/slice_test.cc:710-756

Per CLAUDE.md: "Extract helper functions for repeated patterns." The round-trip pattern (encode → wrap in Slice → DecodeHex → compare) appears in all three test blocks. A small helper like VerifyHexRoundTrip(const std::string& input) would reduce duplication.

  • Suggested fix: Extract helper, but this is a minor style nit.
L3. Test could verify specific uppercase output for key byte values — util/slice_test.cc

The all-bytes test verifies round-trip correctness but doesn't explicitly assert that the output is uppercase. While the round-trip through DecodeHex (which accepts both cases) would pass even if lowercase were produced, adding an explicit check like ASSERT_EQ(Slice("\xAB").ToString(true), "AB") would guard the uppercase contract directly. The single-byte test with "41" does partially cover this.

  • Suggested fix: Consider adding one or two explicit uppercase assertions for bytes with A-F digits.
L4. Minor: std::string(2 * size_, '\0') does a memset before overwrite — util/slice.cc:280

The constructor fills the buffer with '\0' before the loop overwrites every byte. This is an extra memset that's theoretically avoidable but hard to eliminate in standard C++. Modern compilers may optimize this away when they can prove the overwrite is complete. The performance benefit from eliminating push_back overhead far outweighs this cost.

  • Suggested fix: No action needed. This is a known limitation of std::string construction.

Cross-Component Analysis

Context Does code execute? Assumptions hold? Action needed?
WritePreparedTxnDB Yes (via logging) Yes — pure data transform None
ReadOnly DB Yes Yes None
Concurrent readers Yes Yes — static constexpr is safe None
32-bit platforms Yes 2*size_ overflow theoretically possible See L1
MSVC/Windows Yes std::string::data() returns char* in C++17 None
Checksum encoding Yes (via ChecksumInt32ToHex) Yes — uppercase preserved None

Positive Observations

  • Excellent benchmark methodology: PR includes before/after perf stat with branch counters, demonstrating the optimization thesis (branch elimination) with hard data.
  • Correct use of lossless_cast: Follows the established codebase pattern (same usage in util/coding.h:109, util/prefix_varint.h:95) rather than using raw reinterpret_cast.
  • Test covers all 256 byte values: The round-trip test with all byte values is thorough and catches any encoding errors.
  • Non-hex path simplification: return {data_, size_} is cleaner than the old result.assign(); return result; and likely slightly faster (avoids constructing an empty string first).
  • kHexChars is 17 bytes: Fits in a single cache line, excellent for the hot loop.
  • Sequential memory access: Both src and p advance linearly, which is optimal for hardware prefetching.

ℹ️ About this response

Generated by Claude Code.
Review methodology: claude_md/code_review.md

Limitations:

  • Claude may miss context from files not in the diff
  • Large PRs may be truncated
  • Always apply human judgment to AI suggestions

Commands:

  • /claude-review [context] — Request a code review
  • /claude-query <question> — Ask about the PR or codebase

external linkage is not required.
to reduce duplication.  Also to verify chars in hex string are valid after encoding.  Add test for hex string DEADBEEF and deadbeef.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

✅ Claude Code Review

Auto-triggered after CI passed — reviewing commit 473e88c


Summary

Clean, well-motivated performance optimization. The lookup-table approach is correct and the benchmarks are compelling (10x reduction in branches, ~2x throughput improvement). No correctness issues found.

High-severity findings (0):

No high-severity findings.

Full review (click to expand)

Findings

🔴 HIGH

None.

🟡 MEDIUM

None.

🟢 LOW / NIT

L1. Pre-existing integer overflow in 2 * size_ -- util/slice.cc:284
  • Issue: 2 * size_ can theoretically overflow size_t when size_ > SIZE_MAX / 2, causing a too-small allocation and buffer overwrite.
  • Root cause: This is a pre-existing issue (the old code had result.reserve(2 * size_) with the same overflow), not introduced by this PR.
  • Suggested fix: No action required for this PR. In practice, a Slice larger than half the address space is impossible on current hardware. If desired in a future PR, add an assert: assert(size_ <= SIZE_MAX / 2).
L2. Minor: VerifyHexRoundTrip is a free function, not inside a test fixture -- util/slice_test.cc:707
  • Issue: The helper function VerifyHexRoundTrip is declared at namespace scope outside any test fixture. This matches existing conventions in this file (e.g., Multiplier at line 39).
  • Suggested fix: No change needed.
L3. Nit: Comment origin reference updated correctly -- util/slice.cc:241
  • Issue: The comment correctly removes the std::to_string(true) reference since toHex is gone. Remaining attribution for fromHex is accurate.
  • Suggested fix: No change needed.

Cross-Component Analysis

Check Result
Lookup table bounds: c >> 4 ∈ [0,15], c & 0xF ∈ [0,15], kHexChars has 16 entries Safe
Buffer sizing: std::string(2*size_, '\0') matches exactly 2*size_ bytes written Correct
lossless_cast<const unsigned char*>(data_): same-size integral pointer cast Valid (matches util/coding.h:109)
std::string::data() mutability: C++17 guarantees writable char* Valid
Empty slice: loop never executes, returns empty string Correct
hex=false: return {data_, size_}std::string(data_, size_) Correct
Output format: uppercase A-F, same as old toHex() Backward compatible
fromHex/toHex linkage: both only used within util/slice.cc Safe

Positive Observations

  1. Significant measurable improvement: 10x branch reduction, ~2x throughput on real workload
  2. Clean implementation: Canonical lookup-table hex encoding
  3. Good use of lossless_cast: Follows CLAUDE.md guidance
  4. Improved hex=false path: Avoids unnecessary default construction
  5. Comprehensive tests: All 256 byte values, round-trip verification

ℹ️ About this response

Generated by Claude Code.
Review methodology: claude_md/code_review.md

Limitations:

  • Claude may miss context from files not in the diff
  • Large PRs may be truncated
  • Always apply human judgment to AI suggestions

Commands:

  • /claude-review [context] — Request a code review
  • /claude-query <question> — Ask about the PR or codebase

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant