4926314: Optimize Reader.read(CharBuffer) #1915
Implement three optimiztations for Reader.read(CharBuffer)
Implement three optimiztations for Reader.read(CharBuffer) Add a code path for heap buffers in Reader#read to use the backing array instead of allocating a new one. Change the code path for direct buffers in Reader#read to limit the intermediate allocation to TRANSFER_BUFFER_SIZE. Implement InputStreamReader#read(CharBuffer) and delegate to StreamDecoder. Implement StreamDecoder#read(CharBuffer) and avoid buffer allocation.
A couple of implementation notes:
I introduced a dedicated path for the on-heap case and directly read into the backing array. This completely avoids the intermediate allocation and copy. Compared to the initial proposal the buffer position is updated. This has to be done because we bypass the buffer and directly read into the array. This also handles the case that #read returns -1.
I am using a c-style array declaration because the rest of the class uses it.
I limit the intermadiate allocation to TRANSFER_BUFFER_SIZE. In order to reduce the total intermediate allocation we now call #read multiple times until the buffer is full or EOF is reached. This changes the behavior slightly as now possibly more data is read. However this should contribute to reduce the overall intermediate allocations.
A lock is acquired to keep the the read atomic. This is consistent with #skip which also holds a lock over multiple #read calls. This is inconsistent with #transferTo which does not acquire a lock over multiple #read calls.
The implementation took inspiration from java.io.InputStream#readNBytes(int).
Since StreamDecoder is a Reader as well we can simply delegate.
Interestingly this was not implemented even though StreamDecoder internally works on NIO buffers.
We see a performance improvement and the elimination of all intermediate allocation.
StreamDecoder#read(char, int, int) and InputStreamReader#read(char, int, int) may get a small improvement as we no longer #slice the buffer.
We see the elimination of all intermediate allocation but a performance penalty because we hit the slow path in #decodeLoop. There is a trade-off here, we could improve the performance to previous levels by introducing intermediate allocation again. I don't know how much the users of off-heap buffers care about introducing intermediate allocation vs maximum throughput.
I was struggling to come up with microbenchmarks because the built in InputStream and Reader implementations are finite but JMH calls the benchmark methods repeatably. I ended up implementing custom infinite InputStream and Reader implementations. The code can be found there:
An Excel with charts can be found here:
I looked for java.io.Reader#read(CharBuffer) users in the JDK and only found java.util.Scanner#readInput(). I wrote a microbenchmark for this but only found minuscule improvements due to regex dominating the runtime.
There seem to be no direct Reader tests in the tier1 suite, the initial code was wrong because I forgot to update the buffer code position but I only found out because some Scanner tests failed.
I changed the JBS issue summary to match the title of this PR so that integration blocker is removed.
How does the off-heap performance of
Some kind of specific test should likely be included.
Note that the more recent copyright year is now 2021.
On 05.01.21 01:53, Brian Burkhalter wrote:
I can look into this, this will take a moment. I guess it would also
Sure. The existing tests in this area seem to be #main-based. Would you
Indeed it is, fixed.
If you like. I was just wondering whether the change to Reader.read(CharBuffer) would be enough.
For new tests we seem to be preferring Tests NG.
On 05.01.21 01:53, Brian Burkhalter wrote:
I left the delegating one in InputStreamReader in but removed all
To be honest backing out of the StreamDecoder changes looks like a good
Looking a bit further I wonder if CharArrayReader and StringReader
On 17.01.21 18:48, Philippe Marschall wrote:
I gave it some more thought and propose to back out of the StreamDecoder
I did have a look at this  for coders LATIN1(0) and UTF16(1) as well
Based on this is propose to add CharArrayReader#read(CharBuffer),
I think the implementation changes here look good. I don't know however whether there is enough coverage in the tests. These should verify that the
@marschall this pull request can not be integrated into
git checkout JDK-4926314 git fetch https://git.openjdk.java.net/jdk master git merge FETCH_HEAD # resolve conflicts and follow the instructions given by git merge git commit -m "Merge master" git push
Is there a way to get test coverage with JTReg tests? I only found  which seems out of date and points to an Oracle internal wiki.
@marschall This change now passes all automated pre-integration checks.
After integration, the commit message for the final commit will be:
At the time when this comment was updated there had been 524 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.
As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@AlanBateman, @bplb) but any other Committer may sponsor as well.