I'm currently writing a linux program that produces colored output on the terminal.
Since the program stdout could redirected into a textfile, or generally to a non-terminal sink, and the methods should stay as general-purpose as possible, I need to call isatty(int fd)
to determine if I should send the ASCII color escape codes.
Since I'm unsure about the performance impact of calling isatty() before each call to printf(), I've implemented a buffer that buffers isatty() results for the first 16 fds:
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#define TERM_NORMAL "\x1b\x5bm"
#define TERM_BRIGHT "\x1b\x5b\x31m"
#define TERM_BOLD "\x1b\x5b\x31m"
#define TERM_BLINK "\x1b\x5b\x35m"
#define TERM_RED "\x1b\x5b\x33\x31m"
//to prevent unnecessary isatty() calls, provide this lookup table
//for the first 16 fds
//0 means that it has not been checked yet
//1 means the fd is not a tty
//2 means the fd is a tty
char isattybuf[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
inline bool isattybuffered(int fd) {
if(fd >= 0 && fd < sizeof(isattybuf)) {
if(!isattybuf[fd])
isattybuf[fd] = isatty(fd) + 1;
return isattybuf[16] - 1;
} else {
return isatty(fd);
}
}
#define colprintf(col, format, ...) \
if(isattybuffered(fileno(stdout))) \
printf(col format TERM_NORMAL, ## __VA_ARGS__); \
else \
printf(format, ## __VA_ARGS__);
#define colfprintf(col, f, format, ...) \
if(isattybuffered(fileno(f))) \
fprintf(f, col format TERM_NORMAL, ## __VA_ARGS__); \
else \
fprintf(f, format, ## __VA_ARGS__);
//for testing
int main() {
colprintf(TERM_BRIGHT TERM_BLINK, "test1\n");
colprintf(TERM_RED TERM_BRIGHT, "test2\n");
}
But this has a few downsides as well:
A alternative solution that will eliminate the first two problems would be to put the buffer variable into a separate c file using the extern
keyword, but will this work even if the code is compiled as a shared library object and used by multiple programs simultanously?
Unfortunately, the ISATTY(3)
manpage does not provide any hints about the method's performance impacts.
UPDATE
I just ran some benchmarks, and it seems isatty()
does one ioctl
syscall every time it is called, taking about 700ns or 500 clock cycles on my x86_64 ARCH system. A write() syscall (as invoked by printf
) takes about the same amount of time, so if isatty()
is not buffered, i lose less 1µs or about half of the performance per output operation (which seems neglegible as compared to the time required for the terminal scrolling, but can become important when redirecting the output into a large textfile). Especially when continually calling printf()
, write
syscalls are only invoked every 4096 bytes, so the code could spend a large portion of it's time waiting for the results of isatty(), so buffering seems to make sense after all.
So I'd still like to hear your opinions on my attempt of buffering, and the problems i mentioned.
A quick benchmark showed that at least on Darwin, isatty isn't cached and it does an ioctl every time. 10 000 checks of file descriptors 0 - 99 took just 0.4 seconds on 2.8GHz i7 (mac). I would say that calling printf costs far more than calling isatty.
Anyway, I would use a function pointer. At the start I would call one isatty and map a pointer to function (printf without ascii / printf with ascii) and then use that pointer.
Martin