Provide direct byte[] read and write for RubyIO#6590
Conversation
Several Java-based consumers of RubyIO write to it without having a RubyString in hand, by wrapping incoming byte[] or ByteList. This patch adds a write path that can accept unwrapped byte[] plus encoding to reduce allocation and follow a fast path. Users that hit this logic, mostly via IOOutputStream: * The Psych ext when dumping to an IO * For stdout and stderr streams provided by Ruby.getError/OutputStream * By Marshal for dumping to a target IO or IO-like * By GzipWriter for writing to a stream * Anyone that calls to_outputstream on an IO Part of work for jruby#6589
|
Note that due to the complexity of passing more than one "out" parameter, IO writes that require transcoding will still allocate a ByteList, but the initial wasted wrapper is not created in any case. |
None of the consumers use the return value, and this path is intended to simulate OutputStream.write, which does not return the number of bytes written, so skip the creation or retrieval of a Fixnum and just return the int.
kares
left a comment
There was a problem hiding this comment.
🥇 these will be very useful.
have couple overload suggestions that would be nice to support as a Java API
|
@kares Thank you for the review! Yeah I think there are more places we could be using these, including in extensions. @jonathanswenson commented on Matrix that he had attempted to modify Puma to use more direct IO calls but without this PR it could only get so far. This also came to my attention because Psych uses this stream abstraction in order to let SnakeYAML dump directly to an IO or IO-like, the former of which should be much more efficient now (and the latter may be worth special-casing common IO-likes like StringIO). I will add the suggested API enhancements and work on adding read support today. Plus benchmarks. |
Status is set heavily for any blocking operations so make this lighter weight.
This eliminates allocation of the struct-like object for every write operation, which should improve the performance of all IO writes.
These mimic OutputStream except for requiring an Encoding be passed in. Encoding could be made optional depending on how we want to handle jruby#6588.
|
With this latest push, IO implements context-free versions of the |
|
Getting performance metrics on this from Ruby is difficult, because most of the key consumers (Psych, Marshal, GzipWriter) have more overhead of their own. However an allocation profile is interesting. Note the drastic reduction in byte[] and ByteList allocation overall. Script: require 'tempfile'
io = Tempfile.new('bench_marshal_dump')
ary = ["foobar"] * 10000
n = 30
while n > 0
Marshal.dump(ary, io)
io.seek(IO::SEEK_SET, 0)
n-=1
endBefore: After: |
enebo
left a comment
There was a problem hiding this comment.
I did not compare the new methods you ported but I trust you nailed it :)
It is for 9.3 too so it does not have the same requirements as a backport (like changing a field type).
Minor MacOS spec cleanup.
* Factor out real IO calc in IOOutputStream constructor * Revert to RubyThread.getStatus to preserve native status * Clean up some C-style declarations
This PR tracks work to address #6589 by adding allocation-free RubyIO read and write paths.