3 ms·
Care to explain?
by fooyc 11y ago
Care to explain?
- bitwize 11y agoTwo iron-clad rules of data processing: * All data has a type * All I/O is asynchronous and interrupt (event) driven At its inception Unix doubled down on stupid, first by encouraging data to be stored and migrated in the form of untyped text streams, which require ad-hoc parsing and unparsing logic at the endpoints; and secondly by failing to provide asynchronous I/O primitives to user space. POSIX later provided an AIO standard, but it was cumbersome to use, bolted on rather than integrated into the OS, and virtually nobody used it. What you want to be able to do is submit I/O requests to the kernel, and have the kernel notify you when they are completed while you wait or do other processing; you could even put the process to sleep until pending I/O is completed and have it take up no CPU during this time. Instead what people do is burn CPU cycles in select/poll loops, which are the I/O equivalent of asking "Are we there yet? Are we there yet? Are we there yet?" over and over. You can fake asynchronicity by doing a bit of processing before asking "are we there yet?" again, but you have to write your processing code in such a manner as to be done piecewise in a loop, and the lower latency you want the more frequently you have to poll (and the more CPU you have to burn polling). This is how Node does "asynchronous" I/O; the VM stops every few instructions to poll for ready FDs, dispatches to callbacks as necessary, and performs parts of pending large I/O operations which can be done non-blocking. (That's the other sucky bit: you can't just call read(2) and write(2) for large chunks of data on an fd that's been opened O_NONBLOCK and expect it all to work; you have to keep reading or writing in a loop when the fd is ready, subtracting the number of bytes successfully written until the entire buffer is processed.) But if something -- a call into a C library for instance -- stops the VM from doing the poll and I/O bits of its main loop, all I/O simply... stops. And your throughput goes into the toilet. Whereas if the runtime had been based on an OS that natively supports interrupt-driven AIO -- like VMS, Windows, or AmigaOS -- it would be extremely difficult to stall the I/O pipeline completely this way. You might be able to stall further I/O calls with a really long operation, but any pending calls already initiated would complete, and the runtime would be properly notified of their completion. And all this stems from the fact that Unix was the Node.js of its day -- an environment designed to make things easy for casual programmers, not to do things correctly.
- crzwdjk 11y ago> you could even put the process to sleep until pending I/O is completed and have it take up no CPU during this time. Instead what people do is burn CPU cycles in select/poll loops That's what select() does though: you give it a timeout, and it goes to sleep until either an fd becomes ready or the timeout expires. As an example, the X server on you typical linux laptop uses select() on a whole bunch of sockets, one for each client, and it both has good enough latency to be interactive, and doesn't burn a lot of CPU doing so. Is this the most convenient programming model for this? Probably not. And of course it still has the issue that a long-running function can delay your getting back into the select loop.
- bitwize 11y agoSit down in front of an Amiga from the 1980s or early 1990s sometime and you will get a whole new perspective on what constitutes "good enough latency to be interactive". In particular, the UI was always perfectly responsive, even with multiple processes and disk accesses going on in the background -- on a 680x0 machine running tens of megahertz. Once you start spawning enough background tasks and pegging the poor kernel's I/O scheduler, your multi-gigahertz Linux box can't match that sort of claim. Select is still not nearly as flexible as Windows I/O completion ports, for instance. You cannot kick off an I/O operation, do some other processing, and then wait for the I/O to complete before you use its results with a select loop. You have to do a little bit of processing, pump and poll, do a little bit more processing, pump and poll, etc. You are wasting cpu time compared to the interrupt-driven way, and if you have many processes going at once this can add up in terms of battery power used and latency suffered. Remember that select(2) and poll(2) are system calls -- and all the overhead that goes with that. The X server, in its effort to implement a GUI which is by nature event driven, historically contained all sorts of hacks in order to simulate the asynchronicity which should come from the OS. And it has always been slow and laggy compared to its counterparts on Windows and Mac OS -- let alone the Amiga where interrupt-driven programming is the norm and the GUI has always been flawlessly responsive even when other tasks are going on. There's also the fact that nonblocking I/O IS NOT ASYNCHRONOUS I/O. If the kernel needs time to perform an I/O operation -- for example, reads and writes from/to disk -- it WILL block your process doing so. All "nonblocking" really means is "read until read buffer is drained, write until write buffer is full, then return". Your process is still stopped during the read/write! With better primitives such as Windows I/O completion ports that map more closely to the asynchronous I/O subsystems of the underlying architecture -- DMA, interrupts, etc. -- that becomes a non-issue. I/O happens transparently in background from an application (and maybe even kernel) perspective, and nothing is blocked. In short, nearly anything at all is better than the Unix model in terms of throughput, latency, and more natural programming style. Unix is actively regressive.