Skip to content

HDDS-15485 Avoid ByteBuffer.wrap on ChunkBuffer.put(byte[]) path#10438

Open
yandrey321 wants to merge 1 commit into
apache:masterfrom
yandrey321:HDDS-15485
Open

HDDS-15485 Avoid ByteBuffer.wrap on ChunkBuffer.put(byte[]) path#10438
yandrey321 wants to merge 1 commit into
apache:masterfrom
yandrey321:HDDS-15485

Conversation

@yandrey321
Copy link
Copy Markdown
Contributor

@yandrey321 yandrey321 commented Jun 4, 2026

What changes were proposed in this pull request?

Avoid ByteBuffer.wrap on ChunkBuffer.put(byte[]) path

before this change every put(byte[], offset, length ):

Allocates a new ByteBuffer view (HeapByteBuffer over the same backing array).
Dispatches to put(ByteBuffer), which for list/incremental buffers runs a segment loop that adjusts the wrapper’s position/limit and copies via ByteBuffer.put(ByteBuffer).

On the client write path, BlockOutputStream.write(byte[], …) calls currentBuffer.put(b, off, writeLen) once per slice that fits in the current chunk buffer. Large writes can do that many times per write() call.

wrap itself is “cheap” (no array copy), but per call it still pays object allocation + dispatch into the ByteBuffer put path. On a hot path called for every slice of every write(byte[]), that cost is measurable under load.

the fix is micro-optimization, but should reduce memory allocations on hot path and reduce number of objects for garbage collection.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-15485

How was this patch tested?

ozone freon, existing unit tests

@amaliujia
Copy link
Copy Markdown
Contributor

amaliujia commented Jun 5, 2026

I read the Javadoc and ByteBuffer.wrap does not copy the bytes but more like creating a view. I assume this means there is no expensive copy operation.

Is the motivation for this PR about performance improvement or something else?

@yandrey321
Copy link
Copy Markdown
Contributor Author

yandrey321 commented Jun 5, 2026

before this change every put(byte[], offset, length ):

Allocates a new ByteBuffer view (HeapByteBuffer over the same backing array). Dispatches to put(ByteBuffer), which for list/incremental buffers runs a segment loop that adjusts the wrapper’s position/limit and copies via ByteBuffer.put(ByteBuffer).

On the client write path, BlockOutputStream.write(byte[], …) calls currentBuffer.put(b, off, writeLen) once per slice that fits in the current chunk buffer. Large writes can do that many times per write() call.

wrap itself is “cheap” (no array copy), but per call it still pays object allocation + dispatch into the ByteBuffer put path. On a hot path called for every slice of every write(byte[]), that cost is measurable under load.

the fix is micro-optimization, but should reduce memory allocations on hot path and reduce number of objects for garbage collection.

I updated the description:

before this change every put(byte[], offset, length ):

Allocates a new ByteBuffer view (HeapByteBuffer over the same backing array).
Dispatches to put(ByteBuffer), which for list/incremental buffers runs a segment loop that adjusts the wrapper’s position/limit and copies via ByteBuffer.put(ByteBuffer).

On the client write path, BlockOutputStream.write(byte[], …) calls currentBuffer.put(b, off, writeLen) once per slice that fits in the current chunk buffer. Large writes can do that many times per write() call.

wrap itself is “cheap” (no array copy), but per call it still pays object allocation + dispatch into the ByteBuffer put path. On a hot path called for every slice of every write(byte[]), that cost is measurable under load.

the fix is micro-optimization, but should reduce memory allocations on hot path and reduce number of objects for garbage collection.

@adoroszlai adoroszlai requested a review from szetszwo June 5, 2026 14:38
@szetszwo
Copy link
Copy Markdown
Contributor

szetszwo commented Jun 5, 2026

... that cost is measurable under load.

@yandrey321 , Could you justify the word "measurable"?

In Java, we usually don't care about (short live) object allocations. Otherwise, all the classes such as String, Integer, etc cannot be used.

@yandrey321
Copy link
Copy Markdown
Contributor Author

@yandrey321 , Could you justify the word "measurable"?

Its around ~0.5-1% on the real system, in microbenchmark it shows ~7% better throughput. It also slightly reduce work for GC (hard to measure). It is not a major performance improvement but a small optimization that improves write path for a scenario when client passes a byte array.

@szetszwo
Copy link
Copy Markdown
Contributor

szetszwo commented Jun 5, 2026

Its around ~0.5-1% on the real system, in microbenchmark it shows ~7% better throughput. ...

@yandrey321 , Please post the actual numbers and how to reproduce it. Thanks.

In Java, we usually don't care about (short live) object allocations. Otherwise, all the classes such as String, Integer, etc cannot be used.

BTW, ByteBuffer.warp here is very similar to Java auto-boxing for primitive types; see https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html

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.

3 participants