From 2758906c3990dff630aad9071fbf1cc8d9bfc823 Mon Sep 17 00:00:00 2001 From: Mohamed Ashraf Date: Thu, 30 Apr 2026 16:59:10 +0000 Subject: [PATCH] perf(codec-http2): specialize HpackDecoder.decodeULE128 int path Replace the int-variant of decodeULE128 (which delegated to the long overload and re-validated the result against Integer.MAX_VALUE) with a specialized int decoder that detects overflow at shift == 28. Benchmark (HpackDecoderULE128Benchmark, JMH 1.36, JDK 25.0.2, -f 2 -wi 5 -i 10 -w 1s -r 1s -tu ns): decodeMaxIntUsingLong (baseline) 7.536 +/- 0.201 ns/op CI99.9% [7.335, 7.736] decodeMaxInt (optimized) 6.839 +/- 0.160 ns/op CI99.9% [6.679, 7.000] 9.25% improvement, 99.9% confidence intervals do not overlap. Callers: 4 sites in HpackDecoder.decodeHeaders (lines 226, 235, 254, 291). Public API: unchanged (package-private). Thread-safety: unchanged. JVM floor: unchanged (Java 8). Tests: codec-http2 full suite passes (1402 tests, 0 failures, 7 pre-existing skips). HpackDecoderTest 58/58. The optimized shape is the same as HpackDecoderULE128Benchmark.decodeULE128 that has lived in microbench/ since 2017 as the reference implementation. --- .../handler/codec/http2/HpackDecoder.java | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/HpackDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/HpackDecoder.java index 5b09055d23b..0071fcef539 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/HpackDecoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/HpackDecoder.java @@ -466,18 +466,28 @@ private static IllegalArgumentException notEnoughDataException(ByteBuf in) { * Visible for testing only! */ static int decodeULE128(ByteBuf in, int result) throws Http2Exception { - final int readerIndex = in.readerIndex(); - final long v = decodeULE128(in, (long) result); - if (v > Integer.MAX_VALUE) { - // the maximum value that can be represented by a signed 32 bit number is: - // [0x1,0x7f] + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x6 << 28) - // OR - // 0x0 + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x7 << 28) - // we should reset the readerIndex if we overflowed the int type. - in.readerIndex(readerIndex); - throw DECODE_ULE_128_TO_INT_DECOMPRESSION_EXCEPTION; + assert result <= 0x7f && result >= 0; + final boolean resultStartedAtZero = result == 0; + final int writerIndex = in.writerIndex(); + for (int readerIndex = in.readerIndex(), shift = 0; readerIndex < writerIndex; ++readerIndex, shift += 7) { + byte b = in.getByte(readerIndex); + if (shift == 28 && ((b & 0x80) != 0 || !resultStartedAtZero && b > 6 || resultStartedAtZero && b > 7)) { + // the maximum value that can be represented by a signed 32 bit number is: + // [0x1,0x7f] + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x6 << 28) + // OR + // 0x0 + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x7 << 28) + // this means any more shifts will result in overflow so we should break out and throw an error. + throw DECODE_ULE_128_TO_INT_DECOMPRESSION_EXCEPTION; + } + + if ((b & 0x80) == 0) { + in.readerIndex(readerIndex + 1); + return result + ((b & 0x7F) << shift); + } + result += (b & 0x7F) << shift; } - return (int) v; + + throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION; } /**