0

I am working on a REST API that has an endpoint to download a file that could be > 2 GB in size. I have read that Java's FileChannel.transferTo(...) will use zero-copy if the OS supports it. My server is running on localhost during development on my MacBook Pro OS 10.11.6.

I compared the following two methods of writing file to response stream:

  1. Copying a fixed number of bytes from FileChannel to WritableByteChannel using transferTo
  2. Reading a fixed number of bytes from FileInputStream into a byte array (size 4096) and writing to OutputStream in a loop.

The time taken for a 5.2GB file is between 20 and 23 seconds with both methods. I tried transferTo with the fixed number of bytes in single transfer set to following values: 4KB (i.e. 4 * 1024), 1MB and 50MB. The time taken to write is in the same range in all the 3 cases.

Time taken is measured from before entering the while-loop to after exiting the while-loop, in which bytes are read from the file. This is all on the server side. The network hop time does not figure into this.

Any ideas on what the reason could be? I am quite sure MacOS 10.11.6 should support zero-copy (i.e. sendfile system call).

EDIT (6/18/2018):

I found the following blog post from 2015, saying that sendfile on MacOS X is broken. Could it be that this problem still exists?

https://blog.phusion.nl/2015/06/04/the-brokenness-of-the-sendfile-system-call/

Ajoy Bhatia
  • 603
  • 6
  • 30
  • Very interesting article find on OS. I think the reason is hidden in this git repos, https://github.com/ecdavis/pants/issues/43 –  Jun 18 '18 at 21:53
  • Thanks for that Github issue link. That is from 2013, which is earlier than the blog post link in my question edit (2015). So I still do not know if sendfile is **still** broken on MacOS X. I think I'll just ask in a Mac forum (Apple-official or some other good one) – Ajoy Bhatia Jun 22 '18 at 23:40
  • FileChannel.transferTo only uses sendfile when transferring a socket, as in a SocketChannel. – Alan Bateman Jun 26 '18 at 10:24
  • @AlanBateman - From the `FileChannel` javadoc https://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.html#transferTo-long-long-java.nio.channels.WritableByteChannel- : _This method is potentially much more efficient than a simple loop that reads from this channel and writes to the target channel. Many operating systems can transfer bytes directly from the filesystem cache to the target channel without actually copying them._ – Ajoy Bhatia Jun 26 '18 at 15:06
  • @AlanBateman - Sorry, I guess I did not read your comment carefully. I thought you said that only writing to a `SocketChannel` uses sendfile. But then, I don't understand what you mean by _"transferring a socket"_ . – Ajoy Bhatia Jun 26 '18 at 16:54
  • The transferTo method can be used to transfer bytes to any writable byte channel. If you call it to transfer to a SocketChannel then it will use sendfile on macOS and you should see a performance benefit. You aren't going to get any benefit from other target channels, except maybe another FileChannel where the implementation will use memory mapping. – Alan Bateman Jun 26 '18 at 17:12
  • The call to `transferTo` is in response to a large file download request, in a REST API controller method. I have a `StreamingResponseBody` of which I override the `public void writeTo(OutputStream out)` method. Is there a way to create a `SocketChannel` such that transferring to it would write to the `OutputStream out` argument of `writeTo`? As of now, I am getting a `WritableByteChannel` by calling `Channels.newChannel(out)`, which does not return a `SocketChannel` as far as I understand. – Ajoy Bhatia Jun 26 '18 at 22:45

1 Answers1

1

The (high) transfer rate that you are quoting is likely close to or at the limit of what a SATA device can do anyway. If my guess is right, you will not see a performance gain reflected in the time it takes to run your test - however there will likely be a change in the CPU load during the test. Given that you have a relatively powerful machine, your CPU and memory are fast enough. Any method (zero-copy or not) will work at the same speed - which is the speed of your disk. However, zero-copy will cause a lot less CPU load and will not grab unnecessary bandwidth from your memory, either. Therefore, you should test different methods and see which one ends up using the least amount of CPU and choose that method for your application.

Leo K
  • 5,189
  • 3
  • 12
  • 27