Skip to content

perf(abi): cache type bit-width parsing in TypeDecoder.getTypeLength#2280

Open
snuderl wants to merge 1 commit intoLFDT-web3j:mainfrom
snuderl:perf/cache-type-length
Open

perf(abi): cache type bit-width parsing in TypeDecoder.getTypeLength#2280
snuderl wants to merge 1 commit intoLFDT-web3j:mainfrom
snuderl:perf/cache-type-length

Conversation

@snuderl
Copy link
Copy Markdown

@snuderl snuderl commented Apr 27, 2026

Summary

TypeDecoder.getTypeLength(Class<T>) is called on every decodeNumeric invocation. For each call it builds a regex ("(Uint|Int)"), compiles it via String.split, splits the class's simple name, and parses an int. The result for a given Class<?> never changes, so this PR memoizes it in a ConcurrentHashMap.

Why

String.split(regex) recompiles the Pattern on every invocation (it does not match the single-character fast path), which is genuinely expensive on a hot path. CPU profiles of decoders processing many event logs showed Pattern.compile and String.split as a significant chunk of decode time.

Benchmark

Same harness as #2279, single-JVM, 200k warmup + 2M measure ops, best of 3, OpenJDK 25 / arm64.

Type main (ns/op) patch (ns/op) speedup
Uint256 172 47 3.7x
Int128 193 62 3.1x
Address 190 57 3.3x
Bytes32 132 123 1.07x (does not call getTypeLength)

This patch independently delivers larger numeric-type wins than #2279 (direct constructor), because regex compilation was the dominant cost, not reflection.

Compatibility

  • Cache key is Class<?>. The value (bit width) is derived purely from the class identity, so the cache is safe under concurrent access and does not need invalidation.
  • Classloader leak risk is bounded: the only callers pass NumericType subclasses, and there's a small finite set of those.

Test plan

  • ./gradlew :abi:test passes
  • Benchmark numbers above

🤖 Generated with Claude Code

getTypeLength is called on every decodeNumeric invocation and uses regex
splitting (type.getSimpleName().split(regex)) to extract the bit width
from class names like 'Uint256' or 'Int128'. Since the bit width for a
given Class never changes, cache the result in a ConcurrentHashMap.

This eliminates repeated regex compilation and string splitting on the
hot path. The cache is thread-safe and bounded by the finite number of
numeric type classes.

Co-Authored-By: Blaz Snuderl <blaz.snuderl@gmail.com>
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