AnsynchronousFileChannelclass. 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
FileSystemProviderencapsulates all the operating systems specifics. To amuse everybody I am using a Windows 7 client when I am writing this. Therefeore a
WindowsChannelFactorywhich actually creates the file and calls the
WindowsAsynchronousFileChannelImplwhich 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
Iocpis 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
ThreadPoolresponsible for performing the requested asynchronous I/O operation. The
ThreadPoolactually encapsulates a
ThreadPoolExecutorthat does all the multi-threaded asynchronous task execution management since Java 1.5. Write operations to asnchronous file channels result in calls to the
|Figure 1: Callgraph on open call to asynchronous file channel|
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:
That's enough for now. I have explained the basic concepts and also pointed out that conventional I/O still has its right to exist. In the second post I will introduce some of the issues you may encounter when you use default asynchronous file channels. I will also show how to avoid those issues by applying some more viable settings.
Consider using conventional I/O when only one thread is issueing I/O-operations.
The NIO.2 file channels series:
- Applying custom thread pools
- Closing file channels without loosing data
- I/O operations are not atomic