stdio performance in tango, stdlib, and perl

James Dennett jdennett at acm.org
Sat Mar 24 16:48:18 PDT 2007


Walter Bright wrote:
> James Dennett wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> cout << a << b;
>>>
>>> can't guarantee that a and b will be adjacent in the output. In
>>> contrast,
>>>
>>> printf(format, a, b);
>>>
>>> does give that guarantee. Moreover, that guarantee is not between
>>> separate threads in the same process, it's between whole processes!
>>> Guess which of the two is usable :o).
>>
>> As you appear to be saying that printf has to flush every
>> time it's used, I'd guess that it's unusable for performance
>> reasons alone.
> 
> In order for printf to work right it does not need to flush every time
> (you're right in that would lead to terrible performance). The usual
> thing that printf does is only do a flush if isatty() comes back with
> true. In fact, flushing the output at the end of each printf would not
> mitigate multithreading problems at all. In order for printf to be
> thread safe, all that's necessary is for it to acquire/release the C
> stream lock (C's implementation of stdio has a lock associated with each
> stream).

That would be true, except that Andrei wrote that
the guarantee applied to separate processes, and
that can only be guaranteed if you both use some
kind of synchronization between the processes *and*
flush the stream.

Andrei's claim went beyond mere thread-safety, and
that was what I responded to.

> D's implementation of writef does the same thing. D's writef also wraps
> the whole thing in a try-finally, making it exception safe.
> 
> Iostreams'
>     cout << a << b;
> results in the equivalent of:
>     (cout->out(a))->out(b);
> The trouble is, there's no place to hang the lock acquire/release, nor
> the try-finally. It's a fundamental design problem.

There's a place:

locked(cout) << a << b;

can be made do the job, using RAII to lock at the
start of the expression and unlock at the end.

>> It's also really hard to implement such a
>> guarantee on most platforms without using some kind of
>> process-shared mutex, file lock, or similar.  Does printf
>> really incur that kind of overhead every time something is
>> written to a stream,
> 
> It does exactly one lock acquire/release for each printf, not for each
> character written.

Right.  I certainly did not intend to imply that any
serious design would be silly enough to lock for each
character written (which would be fairly useless
synchronization in any case).

>> or does its implementation make use
>> of platform-specific knowledge on which writes are atomic
>> at the OS level?
>>
>> Within a process, this level of safety could be achieved
>> with only a little (usually redundant) synchronization.
> 
> The problem is such synchronization would be invented and added on by
> the user, making it impossible to combine disparate libraries that write
> to stderr, for example, in a multithreading environment.

Most libraries ought not to do so; coding dependencies
on globals into libraries is generally poor design.

The problem is not that users would have to write
synchronization.  Usually they need to do that. A
problem would be if some low-level locking inside
the I/O subsystems gave the impression that the
user did *not* need to synchronize their own code.

It's not quite as simple as this.  One (possibly
killer) argument for building synchronization into
low-level libraries is to reduce the cost of
dealing with support issues from bemused users
who expected not to have to consider thread-safety
when sharing streams between threads.

-- James



More information about the Digitalmars-d mailing list