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
プロセス呼び出しの「コスト」の中には、リソースを集中的に処理する必要があることがあります。
- 実行可能ファイルはディスクからロードする必要があり、実行可能ファイルが保存されているディスクからバイナリBLOBをロードするにはHDDにアクセスする必要があるため、速度が遅くなります。
- 実行可能ファイルは通常動的ライブラリを使用して構築されるため、実行可能ファイルの一部の補助ファイルもロードする必要があります(つまり、HDDからより多くのバイナリBLOBデータを読み取る必要があります)。
- オペレーティングシステムのオーバーヘッド。呼び出す各プロセスには、プロセス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。
答え2
外部コマンドを実行すると/usr/bin/printf
プロセスが生成されますが、組み込みシェルは生成されません。したがって、3000 個のループには 3000 個のプロセスが生成されるため、速度が遅くなります。
ループの外で実行してこれを確認できます。
答え3
新しいプロセスを作成および設定し、プログラムとそのライブラリの依存関係をロード、実行、初期化、クリーンアップ、および終了するのにかかる時間があるため、タスクを実行するのに必要な実際の時間がはるかにあいまいになります。ここでは、printf
高価な作業についてこれを扱います。実装時間、つまりいいえ他人が保証するもの:
$ time /usr/bin/printf %2000000000s > /dev/null
/usr/bin/printf %2000000000s > /dev/null 13.72s user 1.42s system 99% cpu 15.238 total
$ time busybox printf %2000000000s > /dev/null
busybox printf %2000000000s > /dev/null 1.50s user 0.49s system 95% cpu 2.078 total
$ time bash -c 'printf %2000000000s' > /dev/null
bash -c 'printf %2000000000s' > /dev/null 4.59s user 3.35s system 84% cpu 9.375 total
$ time zsh -c 'printf %2000000000s' > /dev/null
zsh -c 'printf %2000000000s' > /dev/null 1.48s user 0.24s system 81% cpu 2.115 total
$ time ksh -c 'printf %2000000000s' > /dev/null
ksh -c 'printf %2000000000s' > /dev/null 0.48s user 0.00s system 88% cpu 0.543 total
$ time mksh -c 'printf %2000000000s' > /dev/null
mksh -c 'printf %2000000000s' > /dev/null 13.59s user 1.57s system 99% cpu 15.262 total
$ time ash -c 'printf %2000000000s' > /dev/null
ash -c 'printf %2000000000s' > /dev/null 13.74s user 1.42s system 99% cpu 15.214 total
$ time yash -c 'printf %2000000000s' > /dev/null
yash -c 'printf %2000000000s' > /dev/null 13.73s user 1.40s system 99% cpu 15.186 total
ご覧のとおり、少なくともこの点では、GNUはprintf
パフォーマンスに最適化されていません。とにかく、コマンドを最適化することはあまり意味がありません。printf
なぜなら、99.999%のユースケースでタスクを実行するのにかかる時間は、とにかく実行時間によって覆われるからです。ギガバイトのデータを処理できるgrep
コマンドを最適化する方が合理的です。sed
一つランニング。