RE: [Haskell-cafe] Re: Hugsvs GHC (again)was: Re: Somerandomnewbiequestions

On 19 January 2005 16:58, Keean Schupke wrote:
Simon Marlow wrote:
This is what GHC does, if I understand you correctly. The thread running select() does so in its own OS thread, while another OS thread runs the Haskell code. As long as you use -threaded, that is. Oh, and before GHC 6.4 it was done a different way - the scheduler used to do the select() between running Haskell threads.
Cheers, Simon
So this means even though the IO calls block, the other Haskell threads (when run with -threaded) keep running?
Yes, unless the IO is to/from disk on a Unix system. Cheers, Simon

Why is disk a special case? I have never heard that all processes under linux wait for a disk read... The kernel most certainly does not busy wait for disks to respond, so the only alternative is that the process that needs to wait (and only that process) is put to sleep. In which case a second thread would be unaffected. Linux does not busy wait in the Kernel! (don't forget the kernel does read-ahead, so it could be that read really does return 'immediately' and without any delay apart from at the end of file - In which case asynchronous IO just slows you down with extra context switches). Keean. Simon Marlow wrote:
On 19 January 2005 16:58, Keean Schupke wrote:
Simon Marlow wrote:
This is what GHC does, if I understand you correctly. The thread running select() does so in its own OS thread, while another OS thread runs the Haskell code. As long as you use -threaded, that is. Oh, and before GHC 6.4 it was done a different way - the scheduler used to do the select() between running Haskell threads.
Cheers, Simon
So this means even though the IO calls block, the other Haskell threads (when run with -threaded) keep running?
Yes, unless the IO is to/from disk on a Unix system.
Cheers, Simon

Keean Schupke wrote:
Why is disk a special case?
With "slow" streams, where there may be an indefinite delay before the data is available, you can use non-blocking I/O, asynchronous I/O, select(), poll() etc to determine if the data is available. If it is, reading the data is essentially just copying from kernel memory to userspace. If it isn't, the program can do something else while it's waiting for the data to arrive. With files or block devices, the data is always deemed to be "available", even if the data isn't in physical memory. Calling read() in such a situation will block until the data has been read into memory.
I have never heard that all processes under linux wait for a disk read... The kernel most certainly does not busy wait for disks to respond, so the only alternative is that the process that needs to wait (and only that process) is put to sleep. In which case a second thread would be unaffected.
Correct. The point is that maximising CPU utilisation requires the use of multiple kernel threads; select/poll or non-blocking/asynchronous I/O won't suffice.
Linux does not busy wait in the Kernel! (don't forget the kernel does read-ahead, so it could be that read really does return 'immediately' and without any delay apart from at the end of file - In which case asynchronous IO just slows you down with extra context switches).
It doesn't busy wait; it suspends the process/thread, then schedules
some other runnable process/thread. The original thread remains
suspended until the data has been transferred into physical memory.
Reading data from a descriptor essentially falls into three cases:
1. The data is in physical RAM. read() copies the data to the supplied
user-space buffer then returns control to the caller.
2. The data isn't in physical RAM, but is available with only a finite
delay (i.e. time taken to read from block device or network
filesystem).
3. The data isn't in physical RAM, and may take an indefinite amount
of time to arrive (e.g. from a socket, pipe, terminal etc).
The central issue is that the Unix API doesn't distinguish between
cases 1 and 2 when it comes to non-blocking I/O, asynchronous I/O,
select/poll etc. [OTOH, NT overlapped I/O and certain Unix extensions
do distinguish these cases, i.e. data is only "available" when it's in
physical RAM.]
If you read from a non-blocking descriptor, and case 2 applies, read()
will block while the data is read from disk then return the data; it
won't return -1 with errno set to EAGAIN, as would happen with case 3.
If you want to be able to utilise the CPU while waiting for disk I/O
to occur, you have to use multiple kernel threads, with one thread for
each pending I/O operation, plus another one for computations (or
another one for each CPU if you want to obtain the full benefit of
an SMP system).
Even then, you still have to allow for the fact that user-space
"memory" is subject to swapping and demand-paging.
--
Glynn Clements

Glynn Clements wrote:
Keean Schupke wrote:
Why is disk a special case?
With "slow" streams, where there may be an indefinite delay before the data is available, you can use non-blocking I/O, asynchronous I/O, select(), poll() etc to determine if the data is available. [...] With files or block devices, the data is always deemed to be "available", even if the data isn't in physical memory.
I don't think this really captures the reason for the difference. It's not that select chooses not to do anything on the assumption that file access is fast. It's that it /can't/ do anything, because (drum roll) disk files are totally different from streams. The data you read from an input stream is being actively written by someone else. As the producer keeps writing, data will end up buffered in local memory until you read it. select() tells you when there's buffered data to read. If you're reading from a random-access file, there's no way it can tell you when the file data is buffered, because it doesn't know which part of the file you plan to read. The OS may try to guess for readahead purposes, but select()'s behavior can't depend on that guess. If streams were distinguished from random-access files at the OS level, the situation would be different. The fact that you create an input stream on top of a disk file indicates to the OS that you plan to read the file sequentially from that point. It's natural to use the same buffering model that's used for sockets, and select() can tell you when that buffer is non-empty. This is just like readahead, except predictable and under app control to some extent. Since you can create an arbitrary number of streams on a file, this mechanism provides all the functionality of NT's overlapped I/O model and quite a bit more. This is another example of why the world would be better off with the file/stream model. Have I convinced anyone? -- Ben

Ben Rudiak-Gould wrote:
If you're reading from a random-access file, there's no way it can tell you when the file data is buffered, because it doesn't know which part of the file you plan to read. The OS may try to guess for readahead purposes, but select()'s behavior can't depend on that guess.
But surely it does! read only reads the next block... to skip randomly you must seek... therefore the following sequence does this: seek select read The select should block until one disk block from the file is in memory, read is defined such that it will return if some data is ready even if it is not as much as you requested. So in this case if you ask for a complete file, you may just get one block... or more. In other words the API restricts reads to the 'next' block - so seek knows which block needs to be read into memory... I can see no reason why is couldn't work like this even if some unixes might not. Keean.

On Fri, Jan 21, 2005 at 12:42:56PM +0000, Keean Schupke wrote:
Ben Rudiak-Gould wrote:
If you're reading from a random-access file, there's no way it can tell you when the file data is buffered, because it doesn't know which part of the file you plan to read. The OS may try to guess for readahead purposes, but select()'s behavior can't depend on that guess.
But surely it does! read only reads the next block... to skip randomly you must seek... therefore the following sequence does this:
seek select read
The select should block until one disk block from the file is in memory, read is defined such that it will return if some data is ready even if it is not as much as you requested. So in this case if you ask for a complete file, you may just get one block... or more.
In other words the API restricts reads to the 'next' block - so seek knows which block needs to be read into memory...
Wouldn't select always fail, since the block would never be read into memory until you call read? -- David Roundy http://www.darcs.net

David Roundy wrote:
If you're reading from a random-access file, there's no way it can tell you when the file data is buffered, because it doesn't know which part of the file you plan to read. The OS may try to guess for readahead purposes, but select()'s behavior can't depend on that guess.
But surely it does! read only reads the next block... to skip randomly you must seek... therefore the following sequence does this:
seek select read
The select should block until one disk block from the file is in memory, read is defined such that it will return if some data is ready even if it is not as much as you requested. So in this case if you ask for a complete file, you may just get one block... or more.
In other words the API restricts reads to the 'next' block - so seek knows which block needs to be read into memory...
Wouldn't select always fail, since the block would never be read into memory until you call read?
True, unless calling select() automatically triggered read-ahead
(which isn't an unreasonable idea).
In that regard, select/poll is different from non-blocking and
asynchronous I/O. With the latter, you're explicitly asking for data
to be read.
--
Glynn Clements

Glynn Clements wrote:
The central issue is that the Unix API doesn't distinguish between
cases 1 and 2 when it comes to non-blocking I/O, asynchronous I/O, select/poll etc. [OTOH, NT overlapped I/O and certain Unix extensions do distinguish these cases, i.e. data is only "available" when it's in physical RAM.]
This is in direct contradiction to the documentation for select. Select specifically says it returns if a handle in the read list would _not_ block on a read call. Keean.

On Fri, 2005-01-21 at 12:33 +0000, Keean Schupke wrote:
Glynn Clements wrote:
The central issue is that the Unix API doesn't distinguish between
cases 1 and 2 when it comes to non-blocking I/O, asynchronous I/O, select/poll etc. [OTOH, NT overlapped I/O and certain Unix extensions do distinguish these cases, i.e. data is only "available" when it's in physical RAM.]
This is in direct contradiction to the documentation for select. Select specifically says it returns if a handle in the read list would _not_ block on a read call.
The point is that the Unix documentation does not consider the short pause as data is read off your hard drive to be blocking. So that's why select will always report that data is available when you use it with a file handle. It considers blocking only to be pauses of indefinite length like you can get when reading from pipes or sockets. Since in that case you depend on the writer end getting round to giving you more data, whereas the kernel considers data in a file to be always available. Of course as people have pointed out this is not a terribly helpful assumption for slow devices and does not allow overlapping IO with processing. The only way to get that is to use other OS APIs dedicated to async/overlapped IO or to use OS threads. Duncan

Duncan Coutts
The point is that the Unix documentation does not consider the short pause as data is read off your hard drive to be blocking. So that's why select will always report that data is available when you use it with a file handle.
Isn't this also for historic reasons? I.e. disk latency has improved from what? 20ms to perhaps 7ms the last fifteen years. CPU frequency has gone from a few MHz to a few GHz in the same time frame. So while a disk seek used to amount to perhaps a thousand instructions -- hardly worth a task switch, it now amounts to millions of instructions. (All numbers made up, of course) With fast CPUs and distributed memory (NUMA architectures), the difference between cache and global memory is beginning to increase also -- perhaps select() really should wake up when the buffer is available in L2 cache? -kzm -- If I haven't seen further, it is by standing in the footprints of giants

Ketil Malde wrote:
The point is that the Unix documentation does not consider the short pause as data is read off your hard drive to be blocking. So that's why select will always report that data is available when you use it with a file handle.
Isn't this also for historic reasons?
Partly.
But I think that it's also because this functionality wasn't intended
for the purpose which is being discussed, i.e. enabling a process to
obtain maximal CPU utilisation.
For that purpose, explicit overlapped I/O (in all forms) can only ever
be a partial solution, because you still have the issue that "memory"
(i.e. code/data/stack segments) is demand-paged. The only solution
there is multiple threads.
--
Glynn Clements

Glynn Clements
The point is that the Unix documentation does not consider the short pause as data is read off your hard drive to be blocking. So that's why select will always report that data is available when you use it with a file handle.
Isn't this also for historic reasons?
Partly.
But I think that it's also because this functionality wasn't intended for the purpose which is being discussed, i.e. enabling a process to obtain maximal CPU utilisation.
I think it's also because if we considered reading from a slow file to be blocking, we should also consider blocking many other calls which access a disk. In particular anything which takes or returns a filename; resolving a filename may involve reading a slow disk. But in these cases the system doesn't know whether the programmer really intended to do something else while mkdir() is completing, because mkdir() is not performed on a file descriptor on which non-blocking mode could have been set or reset. It would need quite a different API. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
participants (8)
-
Ben Rudiak-Gould
-
David Roundy
-
Duncan Coutts
-
Glynn Clements
-
Keean Schupke
-
Ketil Malde
-
Marcin 'Qrczak' Kowalczyk
-
Simon Marlow