Bashのprintfが/usr/bin/printfよりも速いのはなぜですか?

Bashのprintfが/usr/bin/printfよりも速いのはなぜですか?

printfシステムを呼び出すには2つの方法があります。

$ type -a printf
printf is a shell builtin
printf is /usr/bin/printf
$ file /usr/bin/printf
/usr/bin/printf: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically
linked (uses shared libs), for GNU/Linux 2.6.32,
BuildID[sha1]=d663d220e5c2a2fc57462668d84d2f72d0563c33, stripped

したがって、1つはbash組み込みで、もう1つは適切にコンパイルされた実行可能ファイルです。私はシェルprintfの組み込み機能よりもはるかに速いタスクを実行するプログラムを期待したでしょう。もちろん、組み込み関数はすでにメモリにロードされていますが、実際の実行時間は専用プログラムでより速くなければなりませんか?最高のUnix哲学で、1つのことを非常にうまく実行するように最適化されます。

当然いいえ:

$ >/tmp/foo; time for i in `seq 1 3000`; do printf '%s ' "$i" >> /tmp/foo; done;
real    0m0.065s
user    0m0.036s
sys     0m0.024s

$ >/tmp/foo; time for i in `seq 1 3000`; do /usr/bin/printf '%s ' "$i" >> /tmp/foo; done;   
real    0m18.097s
user    0m1.048s
sys     0m7.124s

@Guruが指摘したように、スレッドを作成するのにかかるコストが/usr/bin/printf。残念ながら、/usr/bin/printf許容できる変数のサイズには制限があるため、比較的短い文字列でのみテストできました。

$ i=$(seq 1 28000 | awk '{k=k$1}END{print k}'); time /usr/bin/printf '%s ' "$i" > /dev/null; 

real    0m0.035s
user    0m0.004s
sys     0m0.028s

$ i=$(seq 1 28000 | awk '{k=k$1}END{print k}'); time printf '%s ' "$i" > /dev/null; 

real    0m0.008s
user    0m0.008s
sys     0m0.000s

組み込みの機能は一貫しており、顕著に高速化されました。これをより明確に説明するために、両方のプロセスが新しいプロセスを開始するようにします。

$ time for i in `seq 1 1000`; do /usr/bin/printf '%s ' "$i" >/dev/null; done;   
real    0m33.695s
user    0m0.636s
sys     0m30.628s

$ time for i in `seq 1 1000`; do bash -c "printf '%s ' $i" >/dev/null; done;   

real    0m3.557s
user    0m0.380s
sys     0m0.508s

私が考えることができる唯一の理由は、印刷された変数が組み込み変数の内側にあり、組み込みbash変数に直接渡すことができることです。速度の違いを説明するのに十分ですか?またどんな要因が作用しますか?

ベストアンサー1

独立 printf

プロセス呼び出しの「コスト」の中には、リソースを集中的に処理する必要があることがあります。

  1. 実行可能ファイルはディスクからロードする必要があり、実行可能ファイルが保存されているディスクからバイナリBLOBをロードするにはHDDにアクセスする必要があるため、速度が遅くなります。
  2. 実行可能ファイルは通常動的ライブラリを使用して構築されるため、実行可能ファイルの一部の補助ファイルもロードする必要があります(つまり、HDDからより多くのバイナリBLOBデータを読み取る必要があります)。
  3. オペレーティングシステムのオーバーヘッド。呼び出す各プロセスには、プロセスIDを生成する必要があるという形式のオーバーヘッドが発生します。また、プロセス環境(環境変数など)などの項目を格納するために入力する必要があるさまざまな構造だけでなく、手順1と2でHDDからロードされたバイナリデータを収容するためにメモリスペースも分割されます。

抜粋/usr/bin/printf

    $ strace /usr/bin/printf "%s\n" "hello world"
    *execve("/usr/bin/printf", ["/usr/bin/printf", "%s\\n", "hello world"], [/* 91 vars */]) = 0
    brk(0)                                  = 0xe91000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd155a6b000
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY)      = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=242452, ...}) = 0
    mmap(NULL, 242452, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fd155a2f000
    close(3)                                = 0
    open("/lib64/libc.so.6", O_RDONLY)      = 3
    read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\357!\3474\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0755, st_size=1956608, ...}) = 0
    mmap(0x34e7200000, 3781816, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x34e7200000
    mprotect(0x34e7391000, 2097152, PROT_NONE) = 0
    mmap(0x34e7591000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x191000) = 0x34e7591000
    mmap(0x34e7596000, 21688, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x34e7596000
    close(3)                                = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd155a2e000
    mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd155a2c000
    arch_prctl(ARCH_SET_FS, 0x7fd155a2c720) = 0
    mprotect(0x34e7591000, 16384, PROT_READ) = 0
    mprotect(0x34e701e000, 4096, PROT_READ) = 0
    munmap(0x7fd155a2f000, 242452)          = 0
    brk(0)                                  = 0xe91000
    brk(0xeb2000)                           = 0xeb2000
    brk(0)                                  = 0xeb2000
    open("/usr/lib/locale/locale-archive", O_RDONLY) = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=99158752, ...}) = 0
    mmap(NULL, 99158752, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fd14fb9b000
    close(3)                                = 0
    fstat(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd155a6a000
    write(1, "hello world\n", 12hello world
    )           = 12
    close(1)                                = 0
    munmap(0x7fd155a6a000, 4096)            = 0
    close(2)                                = 0
    exit_group(0)                           = ?*

/usr/bin/printf上記はスタンドアロンの実行可能ファイルであるため、作成する必要がある追加のリソースに関するアイデアを提供します。

組み込み printf

Bashが呼び出されると、Bashが依存するすべてのライブラリのビルドバージョンprintfとそのバイナリブロブがメモリにロードされます。したがって、このようなことが再び起こってはいけません。

実際にBashに組み込まれている「コマンド」を呼び出すと、すべてがすでにロードされているので、実際に関数を呼び出すことになります。

例えば

system("mycmd")Perlなどのプログラミング言語を使用したことがある場合は、関数()を呼び出すか、バックティック()を使用するのと同じです`mycmd`。上記のいずれかを実行すると、Perlのコア機能を介して提供される機能を使用するのではなく、独自のオーバーヘッドを持つ別々のプロセスを分岐します。

Linuxプロセス管理分析

IBM Developerworks には、Linux プロセスがどのように作成および削除されるか、およびプロセスに関連するさまざまな C ライブラリーのさまざまな側面を詳しく説明する非常に便利な記事があります。記事のタイトルは次のとおりです。Linuxプロセス管理分析 - 作成、管理、スケジューリング、破棄。次のようにも利用可能です。PDF

おすすめ記事