Skip to content

Commit 63a8819

Browse files
authored
Performance improvements of reading and writing through StreamBox (#1732)
* Performance improvements of reading and writing through StreamBox * Optimize StreamBox.ReadInto
1 parent 1c429a6 commit 63a8819

4 files changed

Lines changed: 73 additions & 22 deletions

File tree

Src/IronPython.Modules/nt.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,13 +1802,12 @@ public static PythonTuple waitpid(int pid, int options) {
18021802

18031803
public static int write(CodeContext/*!*/ context, int fd, [NotNone] IBufferProtocol data) {
18041804
try {
1805+
using var buffer = data.GetBuffer();
18051806
PythonContext pythonContext = context.LanguageContext;
18061807
var streams = pythonContext.FileManager.GetStreams(fd);
1807-
using var buffer = data.GetBuffer();
1808-
var bytes = buffer.AsReadOnlySpan();
18091808
if (!streams.WriteStream.CanWrite) throw PythonOps.OSError(9, "Bad file descriptor");
18101809

1811-
return streams.Write(bytes);
1810+
return streams.Write(buffer);
18121811
} catch (Exception e) {
18131812
throw ToPythonException(e);
18141813
}

Src/IronPython/Modules/_fileio.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -351,14 +351,7 @@ public BigInteger readinto([NotNone] IBufferProtocol buffer) {
351351

352352
_checkClosed();
353353

354-
var span = pythonBuffer.AsSpan();
355-
for (int i = 0; i < span.Length; i++) {
356-
int b = _streams.ReadStream.ReadByte();
357-
if (b == -1) return i;
358-
span[i] = (byte)b;
359-
}
360-
361-
return span.Length;
354+
return _streams.ReadInto(pythonBuffer);
362355
}
363356

364357
public override BigInteger readinto(CodeContext/*!*/ context, object buf) {
@@ -444,10 +437,9 @@ public override bool writable(CodeContext/*!*/ context) {
444437
public override BigInteger write(CodeContext/*!*/ context, object b) {
445438
var bufferProtocol = Converter.Convert<IBufferProtocol>(b);
446439
using var buffer = bufferProtocol.GetBuffer();
447-
var bytes = buffer.AsReadOnlySpan();
448440

449441
EnsureWritable();
450-
return _streams.Write(bytes);
442+
return _streams.Write(buffer);
451443
}
452444

453445
#endregion

Src/IronPython/Runtime/BufferProtocol.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -308,23 +308,56 @@ public static void CopyTo(this IPythonBuffer buffer, Span<byte> dest) {
308308
}
309309
}
310310

311+
/// <summary>
312+
/// Obtain the underlying array, if possible.
313+
/// The returned array is unsafe because it should not be written to.
314+
/// </summary>
311315
internal static byte[]? AsUnsafeArray(this IPythonBuffer buffer) {
312316
if (!buffer.IsCContiguous())
313317
return null;
314318

315-
if (buffer.Object is Bytes b)
316-
return b.UnsafeByteArray;
317-
318-
if (buffer.Object is Memory<byte> mem) {
319-
if (MemoryMarshal.TryGetArray(mem, out ArraySegment<byte> seg) && seg.Array != null && seg.Offset == 0 && seg.Count == seg.Array.Length)
319+
ReadOnlySpan<byte> bufdata = buffer.AsReadOnlySpan();
320+
if (buffer.Object is Bytes b) {
321+
if (b.UnsafeByteArray.AsSpan() == bufdata)
322+
return b.UnsafeByteArray;
323+
} else if (buffer.Object is ByteArray ba) {
324+
byte[] arrdata = ba.UnsafeByteList.Data;
325+
if (arrdata.AsSpan() == bufdata)
326+
return arrdata;
327+
} else if (buffer.Object is Memory<byte> mem) {
328+
if (MemoryMarshal.TryGetArray(mem, out ArraySegment<byte> seg) && seg.Array is not null && seg.Array.AsSpan() == bufdata)
320329
return seg.Array;
321330
} else if (buffer.Object is ReadOnlyMemory<byte> rom) {
322-
if (MemoryMarshal.TryGetArray(rom, out ArraySegment<byte> seg) && seg.Array != null && seg.Offset == 0 && seg.Count == seg.Array.Length)
331+
if (MemoryMarshal.TryGetArray(rom, out ArraySegment<byte> seg) && seg.Array is not null && seg.Array.AsSpan() == bufdata)
332+
return seg.Array;
333+
}
334+
335+
return null;
336+
}
337+
338+
/// <summary>
339+
/// Obtain the underlying writable array, if possible.
340+
/// The returned array is unsafe because it can be longer than the buffer.
341+
/// </summary>
342+
internal static byte[]? AsUnsafeWritableArray(this IPythonBuffer buffer) {
343+
if (!buffer.IsCContiguous() || buffer.IsReadOnly)
344+
return null;
345+
346+
Span<byte> bufdata = buffer.AsSpan();
347+
if (buffer.Object is ByteArray ba) {
348+
byte[] arrdata = ba.UnsafeByteList.Data;
349+
if (UseSameMemory(arrdata, bufdata))
350+
return arrdata;
351+
} else if (buffer.Object is Memory<byte> mem) {
352+
if (MemoryMarshal.TryGetArray(mem, out ArraySegment<byte> seg) && seg.Array is not null && UseSameMemory(seg.Array, bufdata))
323353
return seg.Array;
324354
}
325355

326356
return null;
327357
}
358+
359+
private static bool UseSameMemory(byte[] arr, ReadOnlySpan<byte> span)
360+
=> arr.Length >= span.Length && arr.AsSpan(0, span.Length) == span;
328361
}
329362

330363
public ref struct BufferBytesEnumerator {

Src/IronPython/Runtime/PythonFileManager.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,44 @@ public byte[] Read(int count) {
109109
return buffer;
110110
}
111111

112-
public int Write(ReadOnlySpan<byte> bytes) {
112+
public int ReadInto(IPythonBuffer buffer) {
113113
#if NETCOREAPP
114+
return _readStream.Read(buffer.AsSpan());
115+
#else
116+
byte[]? bytes = buffer.AsUnsafeWritableArray();
117+
if (bytes is not null) {
118+
return _readStream.Read(bytes, 0, buffer.NumBytes());
119+
}
120+
121+
const int chunkSize = 0x400; // 1 KiB
122+
bytes = new byte[chunkSize];
123+
var span = buffer.AsSpan();
124+
for (int pos = 0; pos < span.Length; pos += chunkSize) {
125+
int toRead = Math.Min(chunkSize, span.Length - pos);
126+
int hasRead = _readStream.Read(bytes, 0, toRead);
127+
bytes.AsSpan(0, hasRead).CopyTo(span.Slice(pos));
128+
if (hasRead < toRead) return pos + hasRead;
129+
}
130+
return span.Length;
131+
#endif
132+
}
133+
134+
public int Write(IPythonBuffer buffer) {
135+
int count;
136+
#if NETCOREAPP
137+
ReadOnlySpan<byte> bytes = buffer.AsReadOnlySpan();
138+
count = bytes.Length;
114139
_writeStream.Write(bytes);
115140
#else
116-
_writeStream.Write(bytes.ToArray(), 0, bytes.Length);
141+
byte[] bytes = buffer.AsUnsafeArray() ?? buffer.AsUnsafeWritableArray() ?? buffer.ToArray();
142+
count = buffer.NumBytes();
143+
_writeStream.Write(bytes, 0, count);
117144
#endif
118145
_writeStream.Flush(); // IO at this level is not supposed to buffer so we need to call Flush.
119146
if (!IsSingleStream) {
120147
_readStream.Seek(_writeStream.Position, SeekOrigin.Begin);
121148
}
122-
return bytes.Length;
149+
return count;
123150
}
124151

125152
public void Flush() {

0 commit comments

Comments
 (0)