7 ms·
Because it's doing something else. Saying stdio is slow isn't the same thing as saying "they should just do what these other guys are doing" -- they can't do t
by geocar 6y ago
Because it's doing something else.
Saying stdio is slow isn't the same thing as saying "they should just do what these other guys are doing" -- they can't do that without breaking everyone who uses stdio, and a lot of people use stdio because being slow isn't a problem they need to solve.
Meanwhile, when being slow is a problem you need to solve, just because there are undoubtedly some quick wins in just about every stdio implementation doesn't mean there would be much value chasing them. Let's say for every 20% improvement in stdio you can find there, you could get a 20x improvement in your application some other way.
And there are a lot of other ways: More formatting and IO libraries than you can shake a stick at, so it's easy to think about the things you're paying for by using stdio that you don't need and simply find something else that doesn't have those things.
Some examples:
- Compatibility: stdio offers a lot of options for no better reason than it's always offered them. This creates a certain amount of bloat and slowdown that can't be removed without breaking some other application that expects this behaviour.
- Edge cases: More on that point, stdio is expected to handle a lot of situations to handle the application might never get into, but they have to test for anyway. Other libraries don't.
- Locking: FILE* needs to be locked for concurrent access to get things like the file position or to update the buffer. Many implementations have an _unlocked() implementation of stdio operations that skip the lock, but the only way to know if they're safe is to know there's no part of the application (or a library) that could be using that stream in another thread. It also nearly doubled the number of API calls stdio offers.
- Tuneables with bad defaults: Most people don't ever use setvbuf because choosing a good value is hard. Pick a value too big, and you'll see massive delays with data coming out, and too small and you waste time on syscalls. If people do use this, they choose something like line buffering (usually for logfiles) which means you're doing a syscall per line.
- Impedance mismatch: fwrite() might look* like write() but it doesn't look anything like writev() or sendmmsg(); Similarly fread() versus recvmmsg() or readv(). stdio would need a bunch of new API calls (like the _unlocked() calls) just to keep up.
- Abstraction in implementation: stdio probably uses malloc which is also slow, but taking an allocator as an argument would require a whole new API. Meanwhile, it is trivial to stack-allocate a buffer for writing your log-line and flush it out with a single write().
There are others.
I like to say that writing fast software is 99% doing only what you need to, and being good at writing fast software requires being brutally narrow about that "need".
- adrianN 6y agoIf somebody finds a 20% improvement in stdio, every program in the world using stdio benefits from that improvement.
- geocar 6y agoYou're right in principle, but I think you're overestimating both the cost of discovery and the value of a 20% improvement: A 20% improvement on a microbenchmark (say float to string conversion) when only 0.01% of your application run-time was spent converting floats to strings nets you fuck all. It might also take weeks-to-months to identify some improvement and get it integrated into each libc implementation. It's so much easier, and the gains are so much bigger to just change the application, and given people's dependency on stdio is usually quite low, that's what most people do.
- chrchang523 6y agoMy experience is primarily in scientific software which has more string<->number conversions than the norm, so others' mileage may vary, but I can confirm that a lot of widely used C and C++ programs are significantly slowed down by their use of standard library I/O functions. Often written by domain experts with more than a decade of experience, not noobs. Yes, once a particular programmer knows about the problem, they can solve it pretty quickly by switching to faster and narrower functions. But this is happening one programmer at a time. Practically everyone is still learning the wrong default, and switching away only after they've noticed they've been burned by it, and scientific programmers at least are being burned by enough other things that there's no guarantee that they'll ever notice this detail, even if they're generally very competent. (We're initially trained to not worry much about this sort of constant factor, after all.) The lowest-hanging fruit I see is in the compiler enhancement domain. They already e.g. inspect printf-family format strings, redirect the containing function call to fputs/memcpy when the string contains no format placeholders, and when there are format placeholders a warning is printed when the number of additional arguments does not match the number of format placeholders. If we could count on snprintf(buf, bufsize, "count: %d", k) being compiled down to little more than a 7- or 8-byte memcpy followed by a call to a dedicated integer-to-string function, I think the associated gain would be far more than "0.01%".