After intensive JMH and real-world benchmarking I dissatisfied noticed that InputStream.transferTo is performing slower than all other known methods to move bytes from one stream into another in nerly all cases: Even a simple pre-NIO-loop from Java 1.4 performs better!
An easy solution could be to simply increase the buffer size. I noticed that using a randomly chosen bigger size (in my case, 16*8K) provides rather good results and will be as fast a simple read-write-loop in custom code with the same buffer size. But it would still be considerably slower than offloading to the operating system (i. e. using Channels). While channels are just a bit faster still with small files, with medium and big files channels outperform InputStream.transferTo by far, even with the 16*8K buffer.
The reason is obvious: Transfering bytes into and out of the VM is slow, and in particular the more JNI calls that are to be done, the more problematic this is. The perfect solution hence is to not transfer bytes through the VM but offload completely by using channels.
My proposal would be to add two improvements:
* Increase InputStream.DEFAULT_BUFFER_SIZE slightly (e. g. simply double it) to make better use of file caches without running into memory troubles on micro machines. This will be a great benefit already and works with all non-channel-streams.
* Implement ChannelInputStream.transferTo in a way which offloads to the OS:
(1) In case the channel is a FileChannel and the target is a ChannelOutputStream (to be refactored from Channels.newOutputStream's anonymous class), invoke FileChannel.transferTo() passing ChannelOutputStream's ch member to transferTo().
(2) Otherwise, if the target stream is a ChannelOutputStream and it's ch is a FileChannel, invoke ch.transferFrom() passing ChannelInputStream's channel.
(3) Otherwise, if source and target are ChannelInputStream/ChannelOutputStream, use a Buffer and loop over read and write of the buffer.
(4) Otherwise, call super.transferTo() (hence, effectively the default implementation InputStream.transferTo()).
According to my JMH benchmark results, implementing ChannelFileStream.transferTo() in that way would effectively produce the FASTEST possible solution, and outperform ANY OTHER way to copy bytes between streams -- while still using the most easy and comfortable way to call it: sourceStream.transferTo(targetStream).
As I am not an OpenJDK contributor, and as it is rather hard to set up a local development environment for this project, I kindly like to ask the committers of this package to discuss my idea BEFORE I invest time into a PR. Thanks a lot!
If needed, I can post the JMH results and source code of the benchmarks for small and medium files which proofs my claim and shows the massive performance improvement of my solution.
An easy solution could be to simply increase the buffer size. I noticed that using a randomly chosen bigger size (in my case, 16*8K) provides rather good results and will be as fast a simple read-write-loop in custom code with the same buffer size. But it would still be considerably slower than offloading to the operating system (i. e. using Channels). While channels are just a bit faster still with small files, with medium and big files channels outperform InputStream.transferTo by far, even with the 16*8K buffer.
The reason is obvious: Transfering bytes into and out of the VM is slow, and in particular the more JNI calls that are to be done, the more problematic this is. The perfect solution hence is to not transfer bytes through the VM but offload completely by using channels.
My proposal would be to add two improvements:
* Increase InputStream.DEFAULT_BUFFER_SIZE slightly (e. g. simply double it) to make better use of file caches without running into memory troubles on micro machines. This will be a great benefit already and works with all non-channel-streams.
* Implement ChannelInputStream.transferTo in a way which offloads to the OS:
(1) In case the channel is a FileChannel and the target is a ChannelOutputStream (to be refactored from Channels.newOutputStream's anonymous class), invoke FileChannel.transferTo() passing ChannelOutputStream's ch member to transferTo().
(2) Otherwise, if the target stream is a ChannelOutputStream and it's ch is a FileChannel, invoke ch.transferFrom() passing ChannelInputStream's channel.
(3) Otherwise, if source and target are ChannelInputStream/ChannelOutputStream, use a Buffer and loop over read and write of the buffer.
(4) Otherwise, call super.transferTo() (hence, effectively the default implementation InputStream.transferTo()).
According to my JMH benchmark results, implementing ChannelFileStream.transferTo() in that way would effectively produce the FASTEST possible solution, and outperform ANY OTHER way to copy bytes between streams -- while still using the most easy and comfortable way to call it: sourceStream.transferTo(targetStream).
As I am not an OpenJDK contributor, and as it is rather hard to set up a local development environment for this project, I kindly like to ask the committers of this package to discuss my idea BEFORE I invest time into a PR. Thanks a lot!
If needed, I can post the JMH results and source code of the benchmarks for small and medium files which proofs my claim and shows the massive performance improvement of my solution.
- links to
-
Review openjdk/jdk/4263