I searched the web on some technical details about blocking I/O and non blocking I/O and I found several people stating that non-blocking I/O would be faster than blocking I/O. For example in this document.
If I use blocking I/O, then of course the thread that is currently blocked can’t do anything else… Because it’s blocked. But as soon as a thread starts being blocked, the OS can switch to another thread and not switch back until there is something to do for the blocked thread. So as long as there is another thread on the system that needs CPU and is not blocked, there should not be any more CPU idle time compared to an event based non-blocking approach, is there?
Besides reducing the time the CPU is idle I see one more option to increase the number of tasks a computer can perform in a given time frame: Reduce the overhead introduced by switching threads. But how can this be done? And is the overhead large enough to show measurable effects? Here is an idea on how I can picture it working:
- To load the contents of a file, an application delegates this task to an event-based i/o framework, passing a callback function along with a filename
- The event framework delegates to the operating system, which programs a DMA controller of the hard disk to write the file directly to memory
- The event framework allows further code to run.
- Upon completion of the disk-to-memory copy, the DMA controller causes an interrupt.
- The operating system’s interrupt handler notifies the event-based i/o framework about the file being completely loaded into memory. How does it do that? Using a signal??
- The code that is currently run within the event i/o framework finishes.
- The event-based i/o framework checks its queue and sees the operating system’s message from step 5 and executes the callback it got in step 1.
Is that how it works? If it does not, how does it work? That means that the event system can work without ever having the need to explicitly touch the stack (such as a real scheduler that would need to backup the stack and copy the stack of another thread into memory while switching threads)? How much time does this actually save? Is there more to it?
The biggest advantage of nonblocking or asynchronous I/O is that your thread can continue its work in parallel. Of course you can achieve this also using an additional thread. As you stated for best overall (system) performance I guess it would be better to use asynchronous I/O and not multiple threads (so reducing thread switching).
Let’s look at possible implementations of a network server program that shall handle 1000 clients connected in parallel:
- One thread per connection (can be blocking I/O, but can also be non-blocking I/O).
Each thread requires memory resources (also kernel memory!), that is a disadvantage. And every additional thread means more work for the scheduler.
- One thread for all connections.
This takes load from the system because we have fewer threads. But it also prevents you from using the full performance of your machine, because you might end up driving one processor to 100% and letting all other processors idle around.
- A few threads where each thread handles some of the connections.
This takes load from the system because there are fewer threads. And it can use all available processors. On Windows this approach is supported by Thread Pool API.
Of course having more threads is not per se a problem. As you might have recognized I chose quite a high number of connections/threads. I doubt that you’ll see any difference between the three possible implementations if we are talking about only a dozen threads (this is also what Raymond Chen suggests on the MSDN blog post Does Windows have a limit of 2000 threads per process?).
On Windows using unbuffered file I/O means that writes must be of a size which is a multiple of the page size. I have not tested it, but it sounds like this could also affect write performance positively for buffered synchronous and asynchronous writes.
The steps 1 to 7 you describe give a good idea of how it works. On Windows the operating system will inform you about completion of an asynchronous I/O (
OVERLAPPED structure) using an event or a callback. Callback functions will only be called for example when your code calls
bAlertable set to
Some more reading on the web:
- Multiple Threads in the User Interface on MSDN, also shortly handling the cost of creating threads
- Section Threads and Thread Pools says “Although threads are relatively easy to create and use, the operating system allocates a significant amount of time and other resources to manage them.”
- CreateThread documentation on MSDN says “However, your application will have better performance if you create one thread per processor and build queues of requests for which the application maintains the context information.”.
- Old article Why Too Many Threads Hurts Performance, and What to do About It