频繁的isatty()调用对性能的影响

mic*_*c_e 3 c unix linux glibc tty

我正在编写一个在终端上生成彩色输出的linux程序.由于程序标准输出可以重定向到文本文件,或者通常重定向到非终端接收器,并且方法应该保持尽可能通用,我需要调用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");
}
Run Code Online (Sandbox Code Playgroud)

但这也有一些缺点:

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

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

遗憾的是,该ISATTY(3)联机帮助页未提供有关该方法性能影响的任何提示.

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

所以我仍然想听听你对我缓冲尝试的看法,以及我提到的问题.

Kve*_*eri 6

一个快速的基准表明,至少在达尔文上,isatty并没有被缓存,而且每次都会进行一次ioctl.文件描述符0 - 99的10 000次检查在2.8GHz i7(mac)上仅用了0.4秒.我会说调用printf的成本远远超过调用isatty.

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

马丁

  • `isatty` 无法缓存,因为它无法知道目标文件描述符是否在调用之间已被替换为不同的文件描述符。 (2认同)