Skip to content

perf: Remove interface#836

Open
ldintr wants to merge 4 commits into
mainfrom
RemoveInterface
Open

perf: Remove interface#836
ldintr wants to merge 4 commits into
mainfrom
RemoveInterface

Conversation

@ldintr
Copy link
Copy Markdown
Contributor

@ldintr ldintr commented May 20, 2026

Description:

The first commit is easier to go through, touches a lot of signature. The rest can be squashed

Bench:

Current:

Benchmark (type) Mode Cnt Score Error Units
GenericParserQuickBench.bench NotCacheableAccountIDType thrpt 15 458.108 ± 2.490 ops/us
https://github.com/hashgraph/pbj/actions/runs/26190721867/job/77058132411

main:

Benchmark (type) Mode Cnt Score Error Units
GenericParserQuickBench.bench NotCacheableAccountIDType thrpt 15 458.108 ± 2.490 ops/us
https://github.com/hashgraph/pbj/actions/runs/26180014678/job/77020208498

Checklist

  • [x ] Documented (Code comments, README, etc.)
  • [x ] Tested (unit, integration, etc.)

ldintr added 3 commits May 20, 2026 17:28
Signed-off-by: ldintr <levo.d@swirldslabs.com>
Signed-off-by: ldintr <levo.d@swirldslabs.com>
Signed-off-by: ldintr <levo.d@swirldslabs.com>
@ldintr ldintr requested review from a team as code owners May 20, 2026 21:34
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

JUnit Test Report

   521 files  ±0     521 suites  ±0   29s ⏱️ -1s
 1 519 tests ±0   1 515 ✅ ±0   4 💤 ±0  0 ❌ ±0 
10 407 runs  ±0  10 379 ✅ ±0  28 💤 ±0  0 ❌ ±0 

Results for commit 82b64da. ± Comparison against base commit 03830e4.

This pull request removes 6 and adds 6 tests. Note that renamed tests count towards both.
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [1] FLOAT, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d49cd30@1f2d0ca2, [0.1, 0.5, 100.0], 12, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d49cf58@e353e1d
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [1] STRING, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d4a8ad8@2ef812b, [string 1, testing here, testing there], com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d4a8d00@5e0602ff
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [2] BYTES, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d4a8f28@6715a6da, [010203, ff7f0f, 42da07370bff], com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d4a9150@29f86630
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [2] DOUBLE, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d49d180@4687fee7, [0.1, 0.5, 100.0, 1.7653472635472653E240], 32, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d49d3a8@4a0fc665
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [3] BOOL, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d49d5d0@1067bc4c, [true, false, false, true, true, true], 6, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d49d7f8@69ee0861
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [4] ENUM, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d49da20@69f55ea, [0, 2, 1], 3, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000005d49dc48@2b370ca9
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [1] FLOAT, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e49e908@5345552f, [0.1, 0.5, 100.0], 12, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e49eb30@55ba4bff
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [1] STRING, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e4aa780@3b1a3125, [string 1, testing here, testing there], com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e4aa9a8@4199761d
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [2] BYTES, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e4aabd0@18c432ed, [010203, ff7f0f, 42da07370bff], com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e4aadf8@3fae357f
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [2] DOUBLE, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e49ed58@2da8ed80, [0.1, 0.5, 100.0, 1.7653472635472653E240], 32, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e49ef80@d827673
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [3] BOOL, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e49f1a8@7daf167, [true, false, false, true, true, true], 6, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e49f3d0@6df4d8f1
com.hedera.pbj.runtime.ProtoWriterToolsTest ‑ [4] ENUM, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e49f5f8@34893693, [0, 2, 1], 3, com.hedera.pbj.runtime.ProtoWriterToolsTest$$Lambda/0x000000003e49f820@6408a8ac

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

Integration Test Report

    426 files  ±0      426 suites  ±0   24m 41s ⏱️ + 5m 6s
115 030 tests ±0  115 030 ✅ ±0  0 💤 ±0  0 ❌ ±0 
115 274 runs  ±0  115 274 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 82b64da. ± Comparison against base commit 03830e4.

This pull request removes 2 and adds 2 tests. Note that renamed tests count towards both.
com.hedera.pbj.integration.test.ParserNeverWrapsTest ‑ [1] com.hedera.pbj.integration.test.ParserNeverWrapsTest$$Lambda/0x0000000041c78a38@25315eb9
com.hedera.pbj.integration.test.ParserNeverWrapsTest ‑ [2] com.hedera.pbj.integration.test.ParserNeverWrapsTest$$Lambda/0x0000000041c78c80@6f2d916f
com.hedera.pbj.integration.test.ParserNeverWrapsTest ‑ [1] com.hedera.pbj.integration.test.ParserNeverWrapsTest$$Lambda/0x0000000095bee238@150a447c
com.hedera.pbj.integration.test.ParserNeverWrapsTest ‑ [2] com.hedera.pbj.integration.test.ParserNeverWrapsTest$$Lambda/0x0000000095bee480@b069a3a

♻️ This comment has been updated with latest results.

@ldintr ldintr self-assigned this May 20, 2026
@ldintr ldintr changed the title Remove interface pref: Remove interface May 20, 2026
@ldintr ldintr changed the title pref: Remove interface perf: Remove interface May 20, 2026
…erytime

Signed-off-by: ldintr <levo.d@swirldslabs.com>
@ldintr ldintr force-pushed the RemoveInterface branch from 3e2e20c to 82b64da Compare May 20, 2026 23:01
while (input.hasRemaining()) {
// Note: ReadableStreamingData.hasRemaining() won't flip to false
while (input.hasMore()) {
// Note: ReadableStreamingData.hasMore() won't flip to false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: the type name in the comment text should probably be updated.

Comment on lines +12 to +17
byte[] buf = new byte[16 << 10];
int pos, end;
int relLimit;
long absoluteLimit, offset;
boolean seenEOF;
private ReadableSequentialData input;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the fields should be private.

}

public SlimBuffer(byte[] buf) {
this.buf = buf;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want to remove the default initializer from line 12 and move it into a constructor that needs that array initialized. Otherwise, when using this specific constructor at line 23, a new object would allocate a 16K array and then immediately throw it away at this line 24, which is bad for GC and performance in general.

import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;

public class SlimBuffer {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add some javadoc for every public class/method in here. You can use the short syntax with ///, or the long one with /** ... */.

}

private void grow(int minCap) {
int newCap = Math.max(buf.length * 2, 1 << 63 - Long.numberOfLeadingZeros((minCap << 1) - 1));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add parentheses around the 63 - Long... expression. I agree that Java/javac don't need them. But humans who read this code do.

while (true) {
if (pos < relLimit) return true;
if (seenEOF || relLimit < end) return false;
bufferMore();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to happen in a while(true) loop? Or to rephrase, why do we want to be able to call the bufferMore() more than once? Can we rewrite this code to make it clear that this method calls bufferMore() at most once (aka, can we nuke the while(true))?

throw new IllegalArgumentException();
}
if (pos + amount <= relLimit) return;
if (seenEOF) throw new BufferUnderflowException();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this will always throw for an object constructed via SlimBuffer(byte[]). How is this constructor useful?

Comment on lines +101 to +106
if (!seenEOF) {
for (int i = 0; !seenEOF && i < 32; i++) {
bufferMore();
}
if (seenEOF == false) {
throw new RuntimeException(); // logic error
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why this is an error.

I don't understand why the bufferMore() called 32 times is assumed to guarantee to read the entire input stream?

Also, I'm unsure why the limit() getter method is supposed to perform I/O.

Also, I'm unsure why it's okay to read the entire input in memory when it may be very huge. For example, some block objects may occupy over 32MB. We don't want to read them all into memory when trying to parse them.

Comment on lines +130 to +140
public long readVarLong(final boolean zigZag) throws BufferUnderflowException, UncheckedIOException {
long value = 0;
for (int i = 0; i < 10; i++) {
final byte b = readByte();
value |= (long) (b & 0x7F) << (i * 7);
if (b >= 0) {
return zigZag ? (value >>> 1) ^ -(value & 1) : value;
}
}
throw new DataEncodingException("Malformed var int");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an old algorithm. We replaced it recently at #826

You want to implement the new algorithm here.

int maxDepth,
int maxSize)
throws ParseException {
return parse(new SlimBuffer(input), strictMode, parseUnknownFields, maxDepth, maxSize);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will create a new SlimBuffer object for every parse() call in all the existing code in Consensus and Block node. I'm afraid that this may degrade the performance. And I see that you've modified the GenericParserQuickBench to use SlimBuffers natively, which hides this potential performance degradation.

I suggest building a benchmark (or reverting the changes in the aforementioned one) that calls the parse(ReadableSequentialData... at all times, and then run it on the main branch and on the branch of this PR. The numbers would give us an idea of how much the performance of the CN and BN would degrade.

If the degradation looks reasonable in the test (say, at most 5% - but I'll let @OlegMazurov and @alex-kuzmin-hg provide their opinion), then we could proceed with releasing this change and then benchmarking the CN and BN code using our performance team tools before actually upgrading PBJ in those systems.

An alternative approach, and/or if the performance degradation is significant, would be to modify all the code in CN and BN to switch to using the SlimBuffer before upgrading PBJ.

Yet another alternative is to keep existing parse(ReadableSequentialData) implementations as they are and introduce the new parse(SlimBuffer) as an extra API. This latter approach would simplify the migration of the CN and BN code as it could be executed step by step migrating one piece of code at a time.

In general, I'm extremely concerned about this particular change here at this line (and all lines below that create new instances of SlimBuffer silently), and I'd suggest to ask our performance engineers to take a look at this.

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.

2 participants