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

C 频繁调用isatty()对性能的影响,c,linux,unix,glibc,tty,C,Linux,Unix,Glibc,Tty,我目前正在编写一个linux程序,在终端上生成彩色输出。 由于程序stdout可以重定向到文本文件,或者通常重定向到非终端接收器,并且这些方法应该尽可能保持通用性,因此我需要调用isatty(int fd)来确定是否应该发送ASCII颜色转义码 因为我不确定每次调用printf()之前调用isatty()对性能的影响,所以我实现了一个缓冲区,用于缓冲前16个FD的isatty()结果: #include <stdbool.h> #include <stdio.h> #in

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

因为我不确定每次调用printf()之前调用isatty()对性能的影响,所以我实现了一个缓冲区,用于缓冲前16个FD的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");
}
#包括
#包括
#包括
#定义术语\u NORMAL“\x1b\x5bm”
#定义术语\u BRIGHT“\x1b\x5b\x31m”
#定义术语\u BOLD“\x1b\x5b\x31m”
#定义术语\u BLINK“\x1b\x5b\x35m”
#定义术语\u RED“\x1b\x5b\x33\x31m”
//要防止不必要的isatty()调用,请提供以下查找表
//对于前16个FD
//0表示尚未对其进行检查
//1表示fd不是tty
//2表示fd为tty
char isattybuf[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
内联布尔值被缓冲(int fd){
如果(fd>=0&&fd
但这也有一些缺点:

  • 因为它是库头文件的一部分,所以包含头文件的每个c文件都有一个缓冲区数组
  • 相应地,isatty()可能被调用n次,其中n是使用代码的c文件的数量
  • 如果打开一个文件,调用isatty(),然后关闭该文件,然后使用相同的fd打开一个tty,则缓冲区信息可能错误
消除前两个问题的另一种解决方案是使用
extern
关键字将缓冲区变量放入一个单独的c文件中,但即使代码被编译为共享库对象并由多个程序同时使用,这种方法是否有效

不幸的是,
ISATTY(3)
manpage没有提供任何关于方法性能影响的提示

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


所以我还是想听听你对我尝试的缓冲以及我提到的问题的看法。

一个快速的基准测试表明,至少在达尔文,isatty没有被缓存,它每次都执行ioctl。 在2.8GHz i7(mac)上,对文件描述符0-99进行10000次检查只需0.4秒。我认为调用printf的成本远远高于调用isatty

无论如何,我会使用一个函数指针,首先我会调用一个isatty并将一个指针映射到函数(printf不带ascii/printf带ascii),然后使用该指针


Martin

你有没有
strace
你的程序来找出答案?我必须承认,没有,我没有考虑到这种可能性。是的,isatty()确实会在每次被调用时进行(ioctl)系统调用。为什么不按照@Kveri/Martin的建议使用函数指针呢?@Rune:有两个主要原因:(1)我不仅需要一个colprintf方法,还需要一个colfprintf方法,它可以获取任意文件*s作为参数,以及(2)我目前的方式(预处理器),gcc编译器可以为格式字符串/参数不匹配生成编译器警告。
isatty
无法缓存,因为它无法知道目标文件描述符是否已在调用之间被替换为不同的描述符。现在,我知道没有任何缓存。但这也不是不可能的。想象一下,有一个钩子ush one file descriptor的isatty cache on close(),它还需要在
dup2
和其他设备上执行此操作……它需要同步,但不能使用锁定,因为
close
是异步信号安全的,所以它必须以某种方式以原子方式执行此操作……而且。。。[插入许多其他问题,当您试图在一个本应纯粹是系统调用的函数中执行类似这样的额外操作时,会出现这些问题]。