Skip to content

Mark the branchless decompression helpers inline - #270

Open
hhr293 wants to merge 1 commit into
google:mainfrom
hhr293:hhr/inline-branchless-helpers
Open

hhr293 wants to merge 1 commit into
google:mainfrom
hhr293:hhr/inline-branchless-helpers

Conversation

@hhr293

@hhr293 hhr293 commented Oct 21, 2026

Copy link
Copy Markdown

Summary

Mark MemCopy64 (both overloads), ClearDeferred, and DeferMemCopy as inline. These helpers are used only by DecompressBranchless.

In a GCC 11 -O2 -fPIC shared-library build, semantic interposition prevents GCC from inlining these helpers, leaving 14 PLT calls in the decompression hot path. The opaque calls also force deferred_src and deferred_length onto the stack instead of keeping them in registers.

With the helpers inlined:

  • All 14 PLT calls are eliminated.
  • The static instruction count of the two DecompressBranchless instantiations drops from 451 to 274.
  • Static stack-access instructions decrease from 104 to 40.
  • The four helper symbols are no longer exported in this build.

This follows the approach discussed in #251 and supersedes the stale PR #259. It also complements the AArch64 GCC 10 results reported in #251 with measurements on x86-64 GCC 11.

Performance Results

1. Snappy microbenchmarks

Measured using the existing snappy_benchmark with GCC 11, -O2, and BUILD_SHARED_LIBS=ON on x86-64, pinned to one core.

Benchmark Throughput Instructions Cycles Branches
BM_UFlatMedley +42% -21% -30% -32%
BM_UValidateMedley +52% -37% -34% -49%

Throughput is the median of three interleaved rounds of five repetitions. Hardware counters are measured per benchmark iteration by taking the difference between runs of 1200 and 200 iterations and dividing by 1000, which eliminates one-time setup overhead.

Branch mispredictions are essentially unchanged (-2% on BM_UFlatMedley), so the gain comes from executing fewer instructions and branches.

2. End-to-end TPC-H performance

Tested with Apache Gluten + Velox on TPC-H SF300 (3 executors × 4 cores), using GCC 13 builds differing only in the helper-linkage change.

For these Gluten builds, the helpers were given internal linkage using static. In the GCC 11 shared-library build above, static and inline produce identical instruction sequences for DecompressBranchless.

Query Wall-clock time
Q5 -4.3%
Q8 -7.6%
Q9 -5.5%
Combined -5.7%

Results are medians of three rounds. For all three queries, the slowest patched run was faster than the fastest baseline run.

3. Correctness

snappy_unittest passes.

MemCopy64 (both overloads), ClearDeferred and DeferMemCopy are used only by
DecompressBranchless but are ordinary external-linkage functions. When snappy
is built as a shared library, GCC must assume that semantic interposition can
replace them at run time, so it neither inlines them nor calls them directly:
every call in the hot loop goes through the PLT. In a -O2 -fPIC shared build
with GCC 11, DecompressBranchless contains 14 such calls.

The cost is more than the calls themselves. DecompressBranchless passes the
addresses of its deferred_src and deferred_length locals to ClearDeferred and
DeferMemCopy, so as long as those calls are opaque the two variables have to
live on the stack and are stored and reloaded on every tag. With the helpers
inlined they stay in registers. In the same build the two instantiations of
DecompressBranchless shrink from 451 to 274 instructions, and the number of
instructions that access the stack drops from 104 to 40.

Declaring the helpers inline removes all of the calls and stops exporting the
four symbols from libsnappy.so. This is what was agreed in google#251, and replaces
the stale google#259. Declaring them static produces an identical instruction
sequence for DecompressBranchless in this build.

Measured with the existing snappy_benchmark (no new benchmark is needed),
built with GCC 11, -O2, BUILD_SHARED_LIBS=ON on x86-64 and pinned to one core.
Throughput is the median of three interleaved rounds of five repetitions. The
hardware counters are per benchmark iteration, taken as the difference between
runs of 1200 and 200 iterations so that the one-off setup cancels out:

                      throughput  instructions  cycles  branches
  BM_UFlatMedley          +42%        -21%        -30%     -32%
  BM_UValidateMedley      +52%        -37%        -34%     -49%

Branch mispredictions are essentially unchanged (-2% on BM_UFlatMedley), so
the gain comes from executing fewer instructions and branches rather than from
better prediction.

End to end, Apache Gluten with the Velox backend on TPC-H SF300 (3 executors x
4 cores), two builds differing only in giving these helpers internal linkage,
median of three rounds:

  q8        -7.6%
  q9        -5.5%
  q5        -4.3%
  combined  -5.7%

The slowest patched round is faster than the fastest baseline round for every
query. snappy_unittest passes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Lipeng Zhu <lipeng.zhu@intel.com>
Co-authored-by: Wangyang Guo <wangyang.guo@intel.com>
@google-cla

google-cla Bot commented Oct 21, 2026

Copy link
Copy Markdown

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.

This branch has not been deployed

No deployments
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