3

我目前正在编写一个在终端上产生彩色输出的 linux 程序。由于程序 stdout 可以重定向到文本文件,或者通常重定向到非终端接收器,并且这些方法应尽可能保持通用,因此我需要调用isatty(int fd)以确定是否应该发送 ASCII 颜色转义码。

因为我不确定在每次调用 printf() 之前调用 isatty() 对性能的影响,所以我实现了一个缓冲区,用于缓冲前 16 个 fds 的 isatty() 结果:

#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");
}

但这也有一些缺点:

  • 由于它将是库头文件的一部分,因此每个包含头文件的单个c 文件都会有一个缓冲区数组
  • 相应地, isatty() 可能会被调用 n 次,其中 n 是使用代码的 c 文件的数量
  • 如果打开文件,调用 isatty(),然后关闭文件,然后使用相同的 fd 打开 tty,则缓冲区信息可能是错误的

消除前两个问题的另一种解决方案是使用关键字将缓冲区变量放入单独的 c 文件中extern,但是即使代码被编译为共享库对象并被多个程序同时使用,这是否可行?

不幸的是,ISATTY(3)手册页没有提供有关该方法的性能影响的任何提示。

更新 我刚刚运行了一些基准测试,似乎isatty()每次ioctl调用它时都会执行一个系统调用,在我的 x86_64 ARCH 系统上花费大约 700ns 或 500 个时钟周期。一个 write() 系统调用(由 调用printf)大约需要相同的时间,所以如果isatty()没有缓冲,我会损失更少的 1µs 或每个输出操作的大约一半的性能(与终端所需的时间相比,这似乎可以忽略不计滚动,但在将输出重定向到大文本文件时会变得很重要)。特别是在连续调用时printf()write系统调用仅每 4096 字节调用一次,因此代码可能会花费大部分时间等待 isatty() 的结果,因此缓冲似乎是有意义的。

所以我仍然想听听你对我缓冲尝试的意见,以及我提到的问题。

4

1 回答 1

6

一个快速的基准测试表明,至少在 Darwin 上,isatty 没有被缓存,并且每次都会执行一次 ioctl。在 2.8GHz i7 (mac) 上,对文件描述符 0 - 99 进行 10 000 次检查仅需 0.4 秒。我会说调用 printf 的成本远远高于调用 isatty。

无论如何,我会使用函数指针。一开始我会调用一个 isatty 并将一个指针映射到函数(没有 ascii 的 printf / 有 ascii 的 printf),然后使用该指针。

马丁

于 2012-04-22T14:04:56.377 回答