-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support ByteBuffer as a backing storage on JVM #239
Comments
The #135 will be done in the context of this project (at least partially). |
“manipulations with the buffer itself works significantly slower on Android“ |
@VDostoyevskiy some time ago, I ran several kotilnx-io benchmarks on Android and saw a significant slowdown when DirectByteBuffer was used as a backing storage (compared to the baseline with ByteArray as a backing storage). At first glance, it looked like Art's JIT failed in ByteBuffer's methods inlining. |
Slowly processing the task list.
Benchmarking results published here: https://github.com/fzhinkin/kotlinx-io-supplementary-benchmarks#kotlinx-io-supplementary-benchmarks tl;dr |
As it was mentioned, the next step toward deciding if and how byte buffers should be supported is to plug it into the library and run our benchmarks to see how it affects the performance. All the table listed below are available as Google Docs spreadsheet here: https://docs.google.com/spreadsheets/d/19krIuAKL7zVv8zFMKtUeGtCAZcuqRkPp7QWvZPRa784/edit?usp=sharing Raw benchmarking results: https://github.com/Kotlin/kotlinx-io/tree/design/dbb/docs/design/byte-buffers/benchmarking The hypothesis to check is that at least on JVM (or maybe even on Android) we can replace byte arrays storing the data I used code from several git branches for the analysis:
The first two tables below represents results collected using a "core" subset of kotlinx-io benchmarks and their versions ported to androidx-benchmark.
JVM results
Unfortunately, the To shrink the performance gap between byte array and byte buffer based implementations I tried to use Android results For android, I ported most of the core benchmarks to Below are results gathered from a device:
Results suggests that switching to direct byte buffers on Android would lead to a significant performances drop. Collected results are not in the byte buffers favor (especially on Android), however it might not be as bad as it seems in a context of some particular application. Also, these results correspond to To check these two statements, I added It would be fare to blame me for checking one of the worst performing scenarios (string encoding), but JSON is an extremely popular serialization format and its crucial to show good results when using kotlinx-io in the context for JSON-serialization. Below are results collected for both JVM and Android (Subset of serialization benchmarks ported to JVM results
Android results
On JVM, byte buffer-backed segments performs better only in conjunction with Unsafe-access (and that's a separate topic to discuss), without it there are some scenarios where it's be better as well as scenarios where it's worse. [Instead of] Conclusion I don't have a particular conclusion about direct byte buffers use on JVM as to squeeze the max performance from it, we have to use unsafe (the sun.misc/jdk.internal one) and its future in JDK is not that bright (and I was not able to beat ByteBuffers with MemorySegments created from it). For the Android, it seems like there are no benefits from switching to ByteBuffer even though buffer-based I/O (via NIO channels) seems to be much faster compared to I/O operations involving heap-residing containers (but use of off-heap data may still have some benefits). |
I've also checked if having multiple segment types will affect the performance if only the one type is actually in use (the assumption is that at least the JVM will employ the CHA to avoid redundant type checks). There's a branch (that won't compile to any target except JVM) private/polymorphic-segments where Segment was turned into an abstract class with two implementations - one with ByteArray inside (based on the I won't post a large table as above, will just briefly summarize results:
JVM benchmarking results are here and Android benchmarking results are here. |
With all that being said about the performance aspect of ByteBuffers support, it's also worth mentioning that ByteBuffers on JVM and native-pointer-based segments on native would help with supporting memory-mapped files. |
Probably we can support ByteBuffers on JVM without hurting performance on Android by publishing a multi-release jar with a baseline implementation remaining the same (byte-array backed) but with polymorphic segments and BB-support enabled for, let's say, JDK9 and onwards. Android tooling ignores MRJ-stuff while dexing, so the trick might work. 👿 I don't think it's a solution we should/could stick to, but that could solve an issue. |
java.nio.ByteBuffer
is THE data container in Java NIO APIs. Those who need to use features provided only by the NIO APIs (like non-blocking sockets) are doomed to useByteBuffer
for data transferring. Those who need to achieve better performance or use IO interfaces unavailable in Java StdLib will end up using libraries that might roll out their own data containers but usually still allowing to wrap or directly useByteBuffer
(like Netty or Aeron does).It's possible to wrap a heap-allocated byte array (the backing storage for
kotlinx-io
segments) into aHeapByteBuffer
, but the use of heap buffers comes with a cost. The majority of NIO API calls eventually perform a native call. If such a call (for example, a native wrapper for POSIXwrite
) needs data, then NIO will supply it in the form of DirectByteBuffer or a memory address extracted from the DirectByteBuffer. If a user had providedDirectByteBuffer
, then that buffer will be used, but if it was aHeapByteBuffer
, then its content will be copied into an internal cachedDirectByteBuffer
instance and only then passed to the native API. If the buffer is empty, then the copying cost could be neglected, but as the buffer grows, it starts playing a more significant role in overall performance.Besides performance issues with NIO API, a buffer residing in native memory is a necessity when it comes to implementing Java API for not yet supported native IO APIs such as io_uring, send w/ MSG_ZEROCOPY flag, epoll in the edge-triggering mode, etc. The only available option for allocating such a buffer and using it in a wide range of JVM versions supported by the Kotlin is by using
DirectByteBuffer
.Unfortunately, using direct byte buffers is not always an option:
MessageDigest
)So the only viable option might be to support both byte-arrays and
ByteBuffer
s as a backing storage and provide a way to choose what particular implementation to use when starting an app.Tasks:
kotlinx-io
kotlinx-io
performance withDirectByteBuffer
kotlinx-io
w/ BB as a backing storage on JVMSegment
implementationsDirectByteBuffer
-backed segmentsMemorySegment
s usage instead of BBThe text was updated successfully, but these errors were encountered: