Programming, Software and Code

Asynchronous IO in Parrot

There are a few new features which are slated for implementation by Parrot 2.0, which is scheduled for release in January. That sure seems like a long way away, but it's never too early to be thinking about it. There are two large tasks that I'm particularly keen about working on: Garbage-collectable contexts and asynchronous IO. I'm waiting on the former because Allison Randal is doing some serious related work and I don't want to even think about it until her dust settles. The asynchronous IO (AIO) system in Parrot is something that I can start thinking about right now, so that's what I'm going to do.

When we think about normal IO, we think about calling a function that performs an IO operation and then returns when that operation is completed. Think about the "printf" function in C, or the "print" statement in Perl. Both of these are synchronous, or "blocking" IO calls. They take control away from your program, perform their operation, and then return when it is completed, blocking your program from continuing until they are finished. This can be significantly wasteful in some situations, although the effects can be mitigated through aggressive buffering.

AIO, in contrast to blocking IO, is a little different. Instead of making an IO call directly, you make a request for the system to perform the IO action. Control flow returns to your program immediately while the underlying system (usually the Operating System or the Virtual Machine) processes the request. On a multicore system, or just one that supports a good threading model, this can save a lot of time and resources. Plus, it lets your program start preparing the next request while the last one is processing.

Think back to your first class about programming, where the instructor probably drew out a quick diagram about the parts of the computer: The hard disks, the ram, the processor, etc. Storage in your computer moves from largest but slowest (hard disk) to smallest and fastest (processor registers). Writing to an uncached portion of memory can waste a few cycles, but writing to a poorly-buffered HDD can waste a hell of a lot more then that. And the entire time your drive is seeking, writing, and verifying (and maybe encrypting!) the data, your program needs to just sit and wait it out. At least, it needs to wait in a traditional blocking IO system. AIO systems leave the waiting to the operating system, and let your program get on with more important work.

When you make the request, the system usually creates a new thread to process it, or adds it into a queue for an existing thread, or something. The system executes the request on it's own time, and then it invokes a callback function to let your program know that the IO has been handled. Actually, this isn't always the case, users of recent Windows versions have probably heard of I/O Completion Ports, which are asynchronous IO objects that return status results in a queue that can be checked by your program, instead of invoking a callback. It's worth noting that a message-based AIO system could easily be built on top of a callback-based one, so Parrot could easily end up with both. This is yet another simplification on my part, there are several forms of AIO, many of which are described on Wikipedia.

So let's look at some implementation details as they pertain to Parrot:
  1. AIO is going to need some sort of asynchronous execution mechanism. Some operating systems provide an AIO API already that Parrot can piggyback on. Some systems might not, however (although I don't think any of our current target platforms fall into this category). If the OS doesn't have an AIO API available, we could put together our own using Parrot's threads implementation
  2. AIO is going to need a scheduling and dispatching mechanism, both to schedule the outgoing request and to schedule a callback when the request is complete. Parrot has a robust event scheduler already that we can use for this.
  3. At first you might think that we need two IO systems, one for the asynchronous and one for the blocking variants. However, this is not the case. All blocking IO calls can be implementing using AIO primitives. A blocking "print" call, for instance, can be implemented by scheduling an asynchronous write operation and then waiting on some kind of synchronizer (a spinlock or mutex or something). So, once we have AIO we only need to maintain one system (although that means a lot of Parrot's current IO system will need to be closely reviewed, if not reworked)
I'm going to be talking more about Parrot and AIO (and some other Parrot-related projects I have in my todo list) here in the next few days.

This entry was originally posted on Blogger and was automatically converted. There may be some broken links and other errors due to the conversion. Please let me know about any serious problems.