protocolbuffers / protocolbuffers/protobuf

Performance: Optimize Varint Encoding/Decoding with Loop Unrolling (and potentially SVE2)

Open
#26,931 4 comments 1 reaction 1 assignee View on GitHub

@esrauchg is already working on this.

Since May 4, 2026.

c++ feature request keep open
Dominant language
C++
Stars
72k
Forks
16.3k
Avg merge
1d 17h
Merged PRs (30d)
140

Description

This applies to C++ generated code and core library functions (specifically coded_stream.h).

The current Varint encoding/decoding implementation relies on tight loops that can lead to high branch misprediction rates and suboptimal instruction pipelining, especially on modern AArch64 servers. In high-throughput scenarios, Varint processing becomes a bottleneck.

I have implemented a specialized version of UnsafeVarint that uses explicit length checks and helper functions designed for compiler loop unrolling.

  1. Loop Unrolling: By pre-calculating the required bytes and using a fixed-length loop in EncodeBytes, we allow the compiler to unroll the logic, reducing branch misses significantly.
  2. Performance Gains: Initial benchmarks on ARM64 servers show an average 30%+ improvement in encoding performance for a single core.

Proposed Code Snippet:

// Within google/protobuf/io/coded_stream.h

PROTOBUF_ALWAYS_INLINE static uint8_t* EncodeBytes(uint32_t value, uint8_t* ptr, size_t num) {
  for (size_t i = 0; i < num; ++i) {
    ptr[i] = (uint8_t)((value >> (7 * i)) & 0x7F) | (1U << 7);
  }
  ptr[num] = (uint8_t)(value >> (num * 7));
  ptr += (num + 1);
  return ptr;
}

PROTOBUF_ALWAYS_INLINE static uint8_t* UnsafeVarint(uint32_t value, uint8_t* ptr) {
  if (value < (1U << 7)) {
    *ptr = value & 0x7F;
    ++ptr;
  } else if (value < (1U << 14)) {
    ptr = EncodeBytes(value, ptr, 1);
  } else if (value < (1U << 21)) {
    ptr = EncodeBytes(value, ptr, 2);
  } else if (value < (1U << 28)) {
    ptr = EncodeBytes(value, ptr, 3);
  } else {
    ptr = EncodeBytes(value, ptr, 4);
  }
  return ptr;
}
// Similar logic for uint64...

Describe alternatives you've considered
I am also exploring ARM64 SVE2 (Scalable Vector Extension 2) instructions. Specifically, using BEXT (Bit Extract) and BDEP (Bit Deposit) equivalents in SVE2 could potentially:

  • Increase encoding performance to 2.5x of the current implementation.
  • Improve decoding performance by approximately 65%.

Question for Maintainers:
Does the Protobuf architecture currently support or welcome architecture-specific intrinsics (like SVE2) for such core operations? If so, I would like to submit a PR including both the general loop-unrolling optimization and the AArch64-specific SIMD enhancements.

Additional context
The benchmarks were conducted on a standard ARM64 cloud instance. I can provide detailed google/benchmark results if required.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.