Review: std.logger

Jakob Ovrum via Digitalmars-d digitalmars-d at puremagic.com
Thu Jul 24 18:23:21 PDT 2014


On Thursday, 24 July 2014 at 23:40:56 UTC, Jakob Ovrum wrote:
> How often have you seen a formatted log message logged in a 
> loop? I'd wager that happens quite often. Using `format` is a 
> no-go as it completely thrashes the GC, it needs to use 
> `formattedWrite` to write directly to the underlying buffer 
> (such as a file).

To eloborate on this: using `format` like std.logger currently 
does, for short-lived strings that are consumed and discared 
almost immediately in a higher stack frame, is extremely 
inefficient as every string needs at least one heap memory 
allocation (though `format` can easily do *multiple* in one 
call). It's a good way to quickly thrash and fragment the GC 
heap, putting an extreme amount of stress on the collector.

Even C has fprintf. If we don't provide efficient formatted 
writing, people will not use our abstractions.

The solution is to use `formattedWrite` for custom allocation 
behaviour. When I've used `formattedWrite` for this kind of 
problem before, it has generally come down to the following 
patterns; let's assume the underlying sink is a file:

  * One solution is to use `formattedWrite` to write to the file 
directly. Of course, Logger doesn't provide an interface for 
writing partial log messages, or writing log messages in chunks, 
so this would require modifying the basic API. Also, 
`formattedWrite` doesn't guarantee any minimum chunk size, so in 
the worst case, it might result in one write operation per 
character, which is very inefficient.

* Another solution is to use `formattedWrite` to write to a 
stack-allocated character buffer, then subsequently pass it to 
`writeLogMsg`. This is a leaky abstraction because it puts an 
arbitrary upper limit on how long log entries can be. A practical 
compromise is to revert to a heap allocation for entries that 
don't fit in the stack buffer, but we can do better;

* The best solution is a hybrid one. Use `formattedWrite` to 
write to a stack-allocated buffer, then whenever it's full, write 
the contents of the buffer to the underlying sink (this is easily 
doable by passing a delegate to `formattedWrite` that sees the 
stack buffer as an upvalue). Yes, this does require modifying the 
basic logger API to support partial writes, but it gives us 
optimal performance.

The last solution gives us no dynamic memory allocations unless 
an exception is thrown, while still minimizing the number of 
writes to the underlying sink, which is important when writes can 
be expensive, such as writes to a file or a socket.


More information about the Digitalmars-d mailing list