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 ファイルごとに 1 つのバッファー配列が存在します。
  • これに対応して、isatty() は n 回呼び出される可能性があります。ここで、n はコードを使用する C ファイルの数です。
  • ファイルが開かれ、isatty() が呼び出され、ファイルが閉じられ、同じ fd で tty が開かれた場合、バッファ情報が間違っている可能性があります。

最初の 2 つの問題を解決する別の解決策は、externキーワードを使用してバッファ変数を別の c ファイルに入れることですが、コードが共有ライブラリ オブジェクトとしてコンパイルされ、複数のプログラムで同時に使用される場合でも、これは機能しますか?

残念ながら、ISATTY(3)マンページには、メソッドのパフォーマンスへの影響に関するヒントはありません。

更新 いくつかのベンチマークを実行したところ、呼び出されるたびにisatty()1 つのsyscall が実行され、x86_64 ARCH システムで約 700ns または 500 クロック サイクルかかるようです。ioctlwrite() システムコール ( によって呼び出されるprintf) にはほぼ同じ時間がかかるため、isatty()バッファリングされていない場合、出力操作ごとに 1µs 未満または約半分のパフォーマンスが失われます (端末に必要な時間と比較すると、無視できるように見えます)。スクロールしますが、出力を大きなテキストファイルにリダイレクトするときに重要になる可能性があります)。特に を継続的に呼び出す場合printf()writesyscall は 4096 バイトごとにしか呼び出されないため、コードは isatty() の結果を待つ時間の大部分を費やす可能性があるため、結局のところバッファリングは理にかなっているようです。

それで、私のバッファリングの試みと私が言及した問題について、あなたの意見を聞きたいです。

4

1 に答える 1

6

簡単なベンチマークでは、少なくとも Darwin では isatty がキャッシュされておらず、毎回 ioctl を実行することが示されました。ファイル記述子 0 ~ 99 の 10,000 チェックは、2.8GHz i7 (mac) でわずか 0.4 秒かかりました。printf の呼び出しは isatty の呼び出しよりもはるかにコストがかかると言えます。

とにかく、関数ポインタを使用します。最初に isatty を 1 つ呼び出し、ポインターを関数 (ascii なしの printf / ascii 付きの printf) にマップし、そのポインターを使用します。

マーティン

于 2012-04-22T14:04:56.377 に答える