5 ms·
> Which basically expanded back in the day to 65k reads of 1 byte for several MB file. Each fread translated to 65k reads of ReadFile Windows API What software
by Someone 4mo ago
> Which basically expanded back in the day to 65k reads of 1 byte for several MB file. Each fread translated to 65k reads of ReadFile Windows API
What software did that that badly? If the code asks for (up to) 65,536 single byte items, why would you split that into 65,536 calls?
Also, that change changes behavior. The old call could read anything from zero to 65,536 bytes, the new one only can read zero or 65,536 bytes.
(Reading the source of a few implementations, I think most implementations will fill the output buffer with partial objects if the input doesn’t supply an integral number of them, but the return value of fread cannot signal that to the caller)
- micampe 4mo agoA long time ago I worked with someone who read 1 byte at a time from a socket because they insisted data was cached so the kernel was going to batch it magically somehow. It took me days to convince them to measure it.
- quietbritishjim 4mo agoThat's different: you're talking about the application code, like OP. But I think the parent comment's point is that the issue is in the implementation of fread itself in the standard library. It's perfectly reasonable for an application to pass it 1, 65536 (i.e. one byte, up to 65536 times) and expect it not to issue 65536 separate OS calls.
- b112 4mo agoIs it? I get what you're saying, but asking for 1 byte 65536 times, is indeed different than asking for 65536 bytes, 1 time. There may be reasons, such as when you pull off the end of a buffer, it shifts. And the buffer size is 1 byte. Or 10. Or whatever. No, I'm not saying that's why. I'm simply saying there is a difference between asking for 1 byte or 65k bytes of something. Even dd runs the same under Linux. dd bs=10k count=1 is faster than bs=1 count=10k I remember trying to recover some data from a spinning disk, and trying to slowly creep up on the data. So I wanted 1 byte per, I wanted it to nibble, until it hit whatever the errored part was. If I just grabbed the lot, it'd error out from the whole read.
- chadgpt3 4mo agodd is designed to request a certain block size from the kernel. fread is not and should just multiply the two arguments and read that many bytes, just like calloc.
- dspillett 4mo agoAnother possibility for why it needs to be done that way is dealing with error conditions. I've not looked at the code (or even the man pages) and it is a long time since I touched anything that low level, so this might be completely wrong, but if there is an error before the next 64KiB (including just hitting EOF) then the semantics could be different. Asking for 1x64KiB I would expect to just error as there aren't the requested number of bytes. Asking for 64Ki lots of 1 byte might simple error just the same, or it might at least populate the buffer with what it can read, or if the meaning of 1,65536 is actually “up to 64Ki lots of 1B” then it would populate the buffer as far as possible and return the amount read rather than an error condition. If the per-byte option is slow but still fast enough, and dealing with the semantics is less faf, then people will go for that because the tiny time loss is worth the larger effort reduction. Of course this assumes the underlying system doesn't change, as with the “making local code to run as on-demand networked code” example higher in the thread which changes the relative performance characteristics of the two calling methods significantly.
- Someone 4mo agoI glanced at https://github.com/busterb/libc-openbsd/blob/master/stdio/fread.c https://github.com/busterb/libc-openbsd/blob/master/stdio/fr... and https://chromium.googlesource.com/chromiumos/third_party/glibc/+/cvs/libc-960824/stdio/fread.c https://chromium.googlesource.com/chromiumos/third_party/gli.... The latter (as usual when comparing OpenBSD and Linux) is more complex, but both multiply count by size and then go their way. Also, the API contract allows fread to read fewer bytes than requested. I would except any implementation to do that. But maybe, somebody interpreted the contract differently than major OSes, in the sense that a call isn’t allowed to write partial size-sized chunks to user memory and/or advance the file position further than its return value advocates (that, I think, is something that the implementations above can do, and might be considered a bug)
- 4mo ago
- vidarh 4mo agoI used to make it a general rule to start all my optimisation of any network code by running strace and look for excessive read's and write's, because you'd be shocked how many did stuff like that if they didn't know the length of a string, or to read the length first, instead of reading into a buffer. I had to convince people with benchmarks regularly that, yes, you could write the handful of lines to do proper user-space buffering and trivially run rings around any code that did extra context switches, because a lot of people didn't realise the cost difference between system calls and calling their own functions. This included, by the way, the MySQL client library, at one point, which would do small read for length fields instead of larger non-blocking reads into a buffer all the time
- macintux 4mo agoI assumed it was a simple mistake: easy to forget what order the two integers are sent.
- tom_ 4mo agoThe standard says that fread calls fgetc multiple times for each object: > For each object, size calls are made to the fgetc function and the results stored, in the order read, in an array of unsigned char exactly overlaying the object (wording unchanged since C99) If the file is unbuffered, depending on how the implementation handles buffering, and how it interprets the standard, then perhaps it does end up hitting a path where there's 1 ReadFile call per byte... I don't know how most implementations get around this. Presumably it's valid to interpret "calls are made" as "behaving as if calls are made", meaning fread can copy data out of the FILE's buffer directly, or make calls directly to whatever routine fgetc defers to, rather than calling fgetc N times literally. Looks like glibc's fread does this.
- klodolph 4mo agoI think it’s pretty rare for files to be unbuffered like that. AFAIK it’s mostly stderr that ends up unbuffered, at least on Unix-like systems.
- tom_ 4mo agoYou can call setbuf(fp,NULL) after opening, and now the stream is unbuffered. What this means is apparently implementation-dependent. As to why you'd do that? - well, who knows the exact circumstances in this case. Perhaps this was faster in some meaningful case that was relevant to some other project (and then maybe the fread doesn't call fgetc after all!). I'm just speculating. Well-reused code often ends up with stuff that needs rethinking, that, even if noticed, nobody has the time or inclination to attempt to fix.
- Someone 4mo ago> The standard says that fread calls fgetc multiple times for each object: >> For each object, size calls are made to the fgetc function and the results stored, in the order read, in an array of unsigned char exactly overlaying the object Aha! That phrase led me to https://man7.org/linux/man-pages/man3/fread.3p.html https://man7.org/linux/man-pages/man3/fread.3p.html. I consulted https://man7.org/linux/man-pages/man3/fread.3.html https://man7.org/linux/man-pages/man3/fread.3.html and https://man.openbsd.org/fread.3 https://man.openbsd.org/fread.3. Neither mentions that. Now, I checked https://cplusplus.com/reference/cstdio/fread/ https://cplusplus.com/reference/cstdio/fread/. It doesn’t mention it, either. ⇒ this appears to be POSIX-specific. Finally, if somebody implements fread as “For each object, size calls are made to the fgetc function”, it doesn’t matter whether you ask for 1 object of size 65,536 or 65,536 objects of size 1; both would call fgetc 65,536 times.