AnsynchronousFileChannel
class. I am analyzing the new JDK 7 features in depth for a couple of weeks now and I have decided to number my posts consecutively. Just to make sure I don't get confused :-) Here is my 7th post about Java 7 (I admit that - by coincidence - this was also a little confusing). Using NIO.2 asynchronous file channels effectively is a wide topic. There are some things to consider here. I have decided to devide the stuff into four posts. In this first part I will introduce the involved concepts when you use asynchonous file channels. Since these file channels work asynchronously, it is interesting to look at their performance compared to conventional I/O. The second part deals with issues like memory and CPU consumption and explains how to use the new NIO.2 channels safely in a high performance scenario. You also need to understand how to close asynchronous channels without loosing data, that's part three. Finally, in part four, we'll take a look into concurrency. Notice: I won't explain the complete API of asynchronous file channels. There are enough posts out there that do a good job on that. My posts dive more into practical applicability and issues you may have when using asynchronous file channels.
OK, enough vague talking, let's get started. Here is a code snippet that opens an asynchronous channel (line 7), writes a sequence of bytes to the beginning of the file (line 9) and waits for the result to return (line 10). Finally, in line 14 the channel is closed.
Important participants in asynchonous file channel calls
Before I continue to dive into the code, let's introduce quickly the involved concepts in the asynchronous (file) channel galaxy. The callgraph in figure 1 shows the sequence diagram in a call to the
open()
-method of the AsynchronousFileChannel
class. A FileSystemProvider
encapsulates all the operating systems specifics. To amuse everybody I am using a Windows 7 client when I am writing this. Therefeore a WindowsFileSystemProvider
calls the WindowsChannelFactory
which actually creates the file and calls the WindowsAsynchronousFileChannelImpl
which returns an instance of itself. The most important concept is the Iocp
, the I/O completion port. It is an API for performing multiple simultaneous asynchronous input/output operations. A completion port object is created and associated with a number of file handles. When I/O services are requested on the object, completion is indicated by a message queued to the I/O completion port. Other processes requesting I/O services are not notified of completion of the I/O services, but instead check the I/O completion port's message queue to determine the status of its I/O requests. The I/O completion port manages multiple threads and their concurrency. Is you can see from the diagram the Iocp
is a subtype of AsynchronousChannelGroup
. So in JDK 7 asynchronous channels the asynchronous channel group is implemented as an I/O completion port. It owns the ThreadPool
responsible for performing the requested asynchronous I/O operation. The ThreadPool
actually encapsulates a ThreadPoolExecutor
that does all the multi-threaded asynchronous task execution management since Java 1.5. Write operations to asnchronous file channels result in calls to the ThreadPoolExecutor.execute()
method. Figure 1: Callgraph on open call to asynchronous file channel |
Some benchmarks
It's always interesting to look at the performance. Asynchronous non blocking I/O must be fast, right? To find an answer to that question I have made my benchmark analysis. Again, I am using Heinz' tiny benchmarking framework to do that. My machine is an Intel Core i5-2310 CPU @ 2.90 GHz with four cores (64-bit). In a benchmark I need a baseline. My baseline is a simple conventional synchronous write operation into an ordinary file. Here is the snippet:
As you can see in line 25, the benchmark performs a single write operation into an ordinary file. And these are the results:
Test: Performance_Benchmark_ConventionalFileAccessExample_1 Warming up ... EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:365947 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:372298 Starting test intervall ... EPSILON:20:TESTTIME:1000:ACTTIME:1000:LOOPS:364706 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:368309 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:370288 EPSILON:20:TESTTIME:1000:ACTTIME:1001:LOOPS:364908 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:370820 Mean: 367.806,2 Std. Deviation: 2.588,665 Total started thread count: 12 Peak thread count: 6 Deamon thread count: 4 Thread count: 5
The following snippet is another benchmark which also issues a write operation (line 25), this time to an asynchronous file channel:
This is the result of the above benchmark on my machine:
Test: Performance_Benchmark_AsynchronousFileChannel_1 Warming up ... EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:42667 EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:193351 Starting test intervall ... EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:191268 EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:186916 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:189842 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:191103 EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:192005 Mean: 190.226,8 Std. Deviation: 1.795,733 Total started thread count: 17 Peak thread count: 11 Deamon thread count: 9 Thread count: 10
Since the snippets above do the same thing, it's safe to say that asynchronous files channels aren't necessarily faster then conventional I/O. That's an interesting result I think. It's difficult to compare conventional I/O and NIO.2 to each other in a single threaded benchmark. NIO.2 was introduced to provide an I/O technique in highly concurrent scenarios. Therefore asking what's faster - NIO or conventional I/O - isn't quite the right question. The appropriate question was: what is "more concurrent"? However, for now, the results above suggest:
Consider using conventional I/O when only one thread is issueing I/O-operations.
The NIO.2 file channels series:
- Introduction
- Applying custom thread pools
- Closing file channels without loosing data
- I/O operations are not atomic
AsynchronousFileChannel is best suited to cases where you are doing I/O to different parts of the same file at the same time (ie: database type application). I don't think it make sense to compare with using java.io.FileOutputStream doing to write sequentially to a file from one thread as it's always going to be more efficient to do synchronous I/O with an API that provides a synchronous I/O API.
ReplyDeleteAs I said the question is not what's faster, the question is what's "more concurrent"? ;-)
Delete"AsynchronousFileChannel is best suited to cases where you are doing I/O to different parts of the same file at the same time (ie: database type application)"
-> that's one scenario where async channels certainly are the best choice. Another scenario is a global log file in a server app ...
"I don't think it make sense to compare ... as it's always going to be more efficient to do synchronous I/O with an API that provides a synchronous I/O API."
-> that's not entirely true I think. My NIO.2 benchmark uses the default thread pool with a SynchronousQueue which creates a thread per task when you submit tasks in a burst. That's why it's slower. If you're using a fixed thread pool with - for instance - an unbounded LinkedBlockingQueue the asynchronous API may always be faster. And that's possible in any case, even if only one client thread is issueing I/O operations.
For that explained reasons I am very careful with my statements in this article. I am saying it's good to *condider* conventional I/O if only one thread is issueing tasks. Sometimes even one thread using async channels can perform better compared to conventional I/O. And again, the correct question is - like you said in your comment -> what's more concurrent? I'll come back to this question in my fourth part in the series.
Performance_Benchmark_AsynchronousFileChannel_1.run encodes "Hello" to bytes for every I/O operation, I assume this is an oversight and is probably skewing the results.
DeleteYou need to create a ByteBuffer for each task cause it's not thread safe. Therefore the snippets are optimized for their corresponding APIs. Further more, the difference *isn't* due to the ByteBuffer creation, it's the thread creation when you submit tasks in a burst. You can see that when you do the same with a LinkeBlockingQueue und fixed thread count (which is much faster then conventional I/O - from the client perspective).
DeleteI think the comment was about "Hello".getBytes(), not the ByteBuffer.wrap.
DeleteNote that on Windows 7 there doesn't any tasks submitted to thread pool when initiating I/O operations but there are tasks submitted set results or invoke completion handlers.
The reason that read and write always need to submit a task to the thread pool on Windows XP is because older versions of Windows don't support thread agnostic I/O. If you try on Windows Vista or newer then you will see it works differently.
ReplyDeletePerformance_Benchmark_AsynchronousFileChannel_1.run doesn't look right. For starters it only initiates the write operation, it doesn't wait for it to complete. The other thing is that it is calls getBytes and creates a ByteBuffer for each I/O operations, which seems very efficient when compared to the run method in the FileOutputStream test.
Hi! I am on Windows 7, I've corrected that.
DeleteI have chosen not to wait for the result in Performance_Benchmark_AsynchronousFileChannel_1.run 'cause I wanted to illustrate that even if you *don't* wait, conventional I/O is faster in that specific benchmarks.
This is not a NIO.2 series for "starters". It's about applying the stuff once you got familiar with the API. I hope you can still find some usefull information for the daily work.
Cheers, Niklas
Your article "reposted"
ReplyDeletehttp://www.javacodegeeks.com/2012/04/java-7-8-nio2-file-channels-on-test.html
Nice post. I didn't understand one of your comments - "async file channels are best suited when reading different parts of the same file at the same time". What is the technical reason behind async file channels benefiting for this use case? Thanks !
ReplyDelete