Skip to content

Commit 3d785ef

Browse files
committed
Avoid some additional copy steps while reading from io.
1 parent 20b6cc7 commit 3d785ef

File tree

5 files changed

+116
-14
lines changed

5 files changed

+116
-14
lines changed

src/main/java/org/truffleruby/core/thread/ThreadLocalBuffer.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public final class ThreadLocalBuffer {
2121
public final Pointer start;
2222
long remaining;
2323
private final ThreadLocalBuffer parent;
24+
private final long ALIGNMENT = 8L;
25+
private final long ALIGNMENT_MASK = ALIGNMENT - 1;
2426

2527
private ThreadLocalBuffer(Pointer start, ThreadLocalBuffer parent) {
2628
this.start = start;
@@ -73,7 +75,7 @@ public Pointer allocate(RubyThread thread, long size, ConditionProfile allocatio
7375

7476
/* We ensure we allocate a non-zero number of bytes so we can track the allocation. This avoids returning null
7577
* or reallocating a buffer that we technically have a pointer to. */
76-
final long allocationSize = Math.max(size, 4);
78+
final long allocationSize = alignUp(size);
7779
if (allocationProfile.profile(remaining >= allocationSize)) {
7880
final Pointer pointer = new Pointer(cursor(), allocationSize);
7981
remaining -= allocationSize;
@@ -88,6 +90,10 @@ public Pointer allocate(RubyThread thread, long size, ConditionProfile allocatio
8890
}
8991
}
9092

93+
private long alignUp(long size) {
94+
return (size + ALIGNMENT_MASK) & ~ALIGNMENT_MASK;
95+
}
96+
9197
@TruffleBoundary
9298
private ThreadLocalBuffer allocateNewBlock(RubyThread thread, long size) {
9399
// Allocate a new buffer. Chain it if we aren't the default thread buffer, otherwise make a new default buffer.

src/main/java/org/truffleruby/extra/ffi/Pointer.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,24 @@ public void readBytes(long offset, byte[] buffer, int bufferPos, int length) {
185185
UNSAFE.copyMemory(null, address + offset, buffer, Unsafe.ARRAY_BYTE_BASE_OFFSET + bufferPos, length);
186186
}
187187

188+
@TruffleBoundary
189+
public boolean readBytesCheck8Bit(byte[] buffer, int length) {
190+
assert address != 0 || length == 0;
191+
assert buffer != null;
192+
assert length >= 0;
193+
194+
long base = address;
195+
boolean highBitUsed = false;
196+
for (int i = 0; i < length; i++) {
197+
byte aByte = UNSAFE.getByte(null, base + i);
198+
if (aByte < 0) {
199+
highBitUsed = true;
200+
}
201+
buffer[i] = aByte;
202+
}
203+
return highBitUsed;
204+
}
205+
188206
public short readShort(long offset) {
189207
assert address + offset != 0;
190208
return UNSAFE.getShort(address + offset);

src/main/java/org/truffleruby/extra/ffi/PointerNodes.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.truffleruby.core.rope.RopeConstants;
3232
import org.truffleruby.core.rope.RopeNodes;
3333
import org.truffleruby.core.string.RubyString;
34+
import org.truffleruby.core.support.RubyByteArray;
3435
import org.truffleruby.core.numeric.RubyBignum;
3536
import org.truffleruby.core.symbol.RubySymbol;
3637
import org.truffleruby.language.Nil;
@@ -316,6 +317,27 @@ protected RubyString readStringToNull(long address, Nil limit,
316317

317318
}
318319

320+
@Primitive(name = "pointer_read_bytes_to_byte_array", lowerFixnum = { 1, 3 })
321+
public abstract static class PointerReadBytesToArrayNode extends PointerPrimitiveArrayArgumentsNode {
322+
323+
@Specialization
324+
protected Object readBytes(RubyByteArray array, int arrayOffset, long address, int length,
325+
@Cached ConditionProfile zeroProfile,
326+
@Cached RopeNodes.MakeLeafRopeNode makeLeafRopeNode) {
327+
final Pointer ptr = new Pointer(address);
328+
if (zeroProfile.profile(length == 0)) {
329+
// No need to check the pointer address if we read nothing
330+
return nil;
331+
} else {
332+
checkNull(ptr);
333+
final byte[] bytes = array.bytes;
334+
ptr.readBytes(0, bytes, arrayOffset, length);
335+
return nil;
336+
}
337+
}
338+
339+
}
340+
319341
@Primitive(name = "pointer_read_bytes", lowerFixnum = 1)
320342
public abstract static class PointerReadBytesNode extends PointerPrimitiveArrayArgumentsNode {
321343

@@ -337,9 +359,10 @@ protected RubyString readBytes(long address, int length,
337359
} else {
338360
checkNull(ptr);
339361
final byte[] bytes = new byte[length];
340-
ptr.readBytes(0, bytes, 0, length);
362+
final boolean is8Bit = ptr.readBytesCheck8Bit(bytes, length);
341363
final Rope rope = makeLeafRopeNode
342-
.executeMake(bytes, ASCIIEncoding.INSTANCE, CodeRange.CR_UNKNOWN, NotProvided.INSTANCE);
364+
.executeMake(bytes, ASCIIEncoding.INSTANCE, is8Bit ? CodeRange.CR_VALID : CodeRange.CR_7BIT,
365+
length);
343366
final RubyString instance = new RubyString(
344367
coreLibrary().stringClass,
345368
getLanguage().stringShape,

src/main/ruby/truffleruby/core/io.rb

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -141,18 +141,18 @@ def fill(io, max = DEFAULT_READ_SIZE)
141141

142142
count = Primitive.min(unused, max)
143143

144-
buffer = Truffle::POSIX.read_string_at_least_one_byte(io, count)
145-
bytes_read = buffer ? buffer.bytesize : 0
146-
if bytes_read > 0
144+
total_read = 0
145+
Truffle::POSIX.read_to_buffer_at_least_one_byte(io, count) do |buffer, bytes_read|
147146
# Detect if another thread has updated the buffer
148147
# and now there isn't enough room for this data.
149148
if bytes_read > unused
150149
raise RuntimeError, 'internal implementation error - IO buffer overrun'
151150
end
152-
@storage.fill(@used, buffer, 0, bytes_read)
151+
Primitive.pointer_read_bytes_to_byte_array(@storage, @used, buffer.address, bytes_read)
153152
@used += bytes_read
153+
total_read += bytes_read
154154
end
155-
bytes_read
155+
total_read
156156
end
157157

158158
# A request to the buffer to have data. The buffer decides whether
@@ -1265,9 +1265,14 @@ def read_to_separator_with_limit
12651265
# Method G
12661266
def read_all
12671267
str = +''
1268-
until @buffer.exhausted?
1269-
@buffer.fill_from @io
1270-
str << @buffer.shift
1268+
unless @buffer.exhausted?
1269+
if !(tmp_str = @buffer.shift).empty?
1270+
str << tmp_str
1271+
end
1272+
end
1273+
1274+
while (tmp_str = Truffle::POSIX.read_string_at_least_one_byte(@io, InternalBuffer::DEFAULT_READ_SIZE))
1275+
str << tmp_str
12711276
end
12721277

12731278
yield_string(str) { |s| yield s }
@@ -1787,9 +1792,14 @@ def read(length=nil, buffer=nil)
17871792
# If the buffer is already exhausted, returns +""+.
17881793
private def read_all
17891794
str = +''
1790-
until @ibuffer.exhausted?
1791-
@ibuffer.fill_from self
1792-
str << @ibuffer.shift
1795+
unless @ibuffer.exhausted?
1796+
if !(tmp_str = @ibuffer.shift).empty?
1797+
str << tmp_str
1798+
end
1799+
end
1800+
1801+
while (tmp_str = Truffle::POSIX.read_string_at_least_one_byte(self, InternalBuffer::DEFAULT_READ_SIZE))
1802+
str << tmp_str
17931803
end
17941804

17951805
str

src/main/ruby/truffleruby/core/posix.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,19 @@ def self.read_string_at_least_one_byte(io, count)
362362
end
363363
end
364364

365+
def self.read_to_buffer_at_least_one_byte(io, count, &block)
366+
while true
367+
# must call #read_to_buffer in order to properly support polyglot STDIO
368+
bytes_read, errno = read_to_buffer(io, count, &block)
369+
return bytes_read if errno == 0
370+
if errno == EAGAIN_ERRNO
371+
IO.select([io])
372+
else
373+
Errno.handle_errno(errno)
374+
end
375+
end
376+
end
377+
365378
# Used in IO#read_nonblock
366379

367380
def self.read_string_nonblock(io, count, exception)
@@ -405,6 +418,36 @@ def self.read_string_native(io, length)
405418
end
406419
end
407420

421+
def self.read_to_buffer_native(io, length)
422+
fd = io.fileno
423+
buffer = Primitive.io_thread_buffer_allocate(length)
424+
begin
425+
bytes_read = Truffle::POSIX.read(fd, buffer, length)
426+
if bytes_read < 0
427+
bytes_read, errno = bytes_read, Errno.errno
428+
elsif bytes_read == 0 # EOF
429+
bytes_read, errno = 0, 0
430+
else
431+
bytes_read, errno = bytes_read, 0
432+
end
433+
434+
if bytes_read < 0
435+
[-1, errno]
436+
elsif bytes_read == 0 # EOF
437+
[0, 0]
438+
else
439+
yield buffer, bytes_read
440+
[bytes_read, 0]
441+
end
442+
ensure
443+
Primitive.io_thread_buffer_free(buffer)
444+
end
445+
end
446+
447+
def self.read_to_buffer_polyglot(io, count)
448+
raise RuntimeError, 'Not supported yet.'
449+
end
450+
408451
def self.read_string_polyglot(io, length)
409452
fd = io.fileno
410453
if fd == 0
@@ -513,12 +556,14 @@ def self.write_string_nonblock_polyglot(io, string)
513556
if Truffle::Boot.get_option('polyglot-stdio')
514557
class << self
515558
alias_method :read_string, :read_string_polyglot
559+
alias_method :read_to_buffer, :read_to_buffer_polyglot
516560
alias_method :write_string, :write_string_polyglot
517561
alias_method :write_string_nonblock, :write_string_nonblock_polyglot
518562
end
519563
else
520564
class << self
521565
alias_method :read_string, :read_string_native
566+
alias_method :read_to_buffer, :read_to_buffer_native
522567
alias_method :write_string, :write_string_native
523568
alias_method :write_string_nonblock, :write_string_nonblock_native
524569
end

0 commit comments

Comments
 (0)