FileSource and FileSink are always wrapped in Okio.buffer() by the caller — that's the entire point of the unbuffered Source/Sink layer. So the FILE* stdio buffer sitting between Okio's own Buffer and the kernel is pure waste: it adds a redundant memcpy through libc's internal 4–8 KB buffer on every segment transfer.
The JVM gets this right. The call chain is:
JvmOkio.kt wraps a raw FileInputStream/FileOutputStream, not a BufferedInputStream/BufferedOutputStream:
https://github.com/square/okio/blob/f806620cd18dcbafa4eb3ac484ae7985fd92dba9/okio/src/jvmMain/kotlin/okio/JvmOkio.kt#L178-L187
FileInputStream.readBytes calls straight into readBytes in io_util.c:
https://github.com/openjdk/jdk/blob/3c1af6b9c8f3f8b56c7fcfae5df250840fbf7cb4/src/java.base/share/native/libjava/FileInputStream.c#L69-L73
readBytes calls IO_Read directly on the raw fd with a stack-allocated bounce buffer — no FILE*, no stdio:
https://github.com/openjdk/jdk/blob/3c1af6b9c8f3f8b56c7fcfae5df250840fbf7cb4/src/java.base/share/native/libjava/io_util.c#L146
On Unix, IO_Read/IO_Write resolve to bare read(2)/write(2) syscalls:
https://github.com/openjdk/jdk/blob/3c1af6b9c8f3f8b56c7fcfae5df250840fbf7cb4/src/java.base/unix/native/libjava/io_util_md.c#L192
So on JVM every InputStream.read(byte[], off, len) goes Java → JNI → IO_Read → read(2). On native the same operation goes Kotlin → fread → libc internal buffer → read(2), with a pointless copy through a buffer that does nothing Okio isn't already doing.
The fix is to replace fopen/FILE* in variantSource/variantSink with open(2) and sequential read(2)/write(2) on the resulting fd, matching what the JVM has always done.
FileSourceandFileSinkare always wrapped inOkio.buffer()by the caller — that's the entire point of the unbufferedSource/Sinklayer. So theFILE*stdio buffer sitting between Okio's ownBufferand the kernel is pure waste: it adds a redundant memcpy through libc's internal 4–8 KB buffer on every segment transfer.The JVM gets this right. The call chain is:
JvmOkio.ktwraps a rawFileInputStream/FileOutputStream, not aBufferedInputStream/BufferedOutputStream:https://github.com/square/okio/blob/f806620cd18dcbafa4eb3ac484ae7985fd92dba9/okio/src/jvmMain/kotlin/okio/JvmOkio.kt#L178-L187
FileInputStream.readBytescalls straight intoreadBytesinio_util.c:https://github.com/openjdk/jdk/blob/3c1af6b9c8f3f8b56c7fcfae5df250840fbf7cb4/src/java.base/share/native/libjava/FileInputStream.c#L69-L73
readBytescallsIO_Readdirectly on the raw fd with a stack-allocated bounce buffer — noFILE*, no stdio:https://github.com/openjdk/jdk/blob/3c1af6b9c8f3f8b56c7fcfae5df250840fbf7cb4/src/java.base/share/native/libjava/io_util.c#L146
On Unix,
IO_Read/IO_Writeresolve to bareread(2)/write(2)syscalls:https://github.com/openjdk/jdk/blob/3c1af6b9c8f3f8b56c7fcfae5df250840fbf7cb4/src/java.base/unix/native/libjava/io_util_md.c#L192
So on JVM every
InputStream.read(byte[], off, len)goesJava → JNI → IO_Read → read(2). On native the same operation goesKotlin → fread → libc internal buffer → read(2), with a pointless copy through a buffer that does nothing Okio isn't already doing.The fix is to replace
fopen/FILE*invariantSource/variantSinkwithopen(2)and sequentialread(2)/write(2)on the resulting fd, matching what the JVM has always done.