Thursday, April 5, 2012

Java 7: NIO.2 File Channels on the test bench - Part 1 - Introduction

Another blog post about new JDK 7 features. This time I am writing about the new 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.
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.

The NIO.2 file channels series:
- Introduction
- Applying custom thread pools
- Closing file channels without loosing data
- I/O operations are not atomic



9 comments:

  1. 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.

    ReplyDelete
    Replies
    1. As I said the question is not what's faster, the question is what's "more concurrent"? ;-)

      "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.

      Delete
    2. 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.

      Delete
    3. You 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).

      Delete
    4. I think the comment was about "Hello".getBytes(), not the ByteBuffer.wrap.

      Note 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.

      Delete
  2. 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.

    Performance_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.

    ReplyDelete
    Replies
    1. Hi! I am on Windows 7, I've corrected that.

      I 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

      Delete
  3. Your article "reposted"

    http://www.javacodegeeks.com/2012/04/java-7-8-nio2-file-channels-on-test.html

    ReplyDelete
  4. 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