パイプとhere文字列を使用したリソース使用量


16

次の2つを使用して、同じ結果を得ることができますbash

echo 'foo' | cat

そして

cat <<< 'foo'

私の質問は、使用されるリソースに関する限り、これら2つの違いは何ですか、どちらが良いですか?

私の考えでは、パイプを使用している間、余分なプロセスechoとパイプを使用しているのに対し、ここでは文字列ではファイル記述子のみが使用されていcatます。

回答:


17

パイプは、カーネル内のファイルシステムで開かれたファイルであり、ディスク上の通常のファイルとしてはアクセスできません。特定のサイズにのみ自動的にバッファリングされ、いっぱいになると最終的にブロックされます。ブロックデバイスをソースとするファイルとは異なり、パイプはキャラクターデバイスと非常によく似ているため、通常はサポートlseek()されず、通常のファイルのようにそれらから読み取られたデータを再度読み取ることはできません。

here-stringは、マウントされたファイルシステムで作成された通常のファイルです。シェルはファイルを作成し、その記述子を保持しますが、ファイルへのバイトの書き込み/読み取りが行われる前に、その唯一のファイルシステムリンクをすぐに削除(および削除)します。カーネルは、すべてのプロセスがすべての記述子を解放するまで、ファイルに必要なスペースを維持します。そのような記述子から読み取っている子がそうする能力を持っている場合、それは巻き戻されlseek()、再び読むことができます。

どちらの場合も、トークン<<<|はファイル記述子を表し、必ずしもファイル自体ではありません。次のようなことを行うことで、何が起こっているかをよりよく理解できます。

readlink /dev/fd/1 | cat

...または...

ls -l <<<'' /dev/fd/*

2つのファイルの最も大きな違いは、here-string / docがほぼすべて一度に行われることです。シェルは、読み取り記述子を子に提供する前にすべてのデータを書き込みます。一方、シェルは適切な記述子でパイプを開き、子を分岐してパイプの管理を行います。したがって、両端で同時に書き込み /読み取りが行われます。

ただし、これらの違いは一般的に正しいだけです。私が知っている限りでは(実際にはそれほどではありません)、これは、<<<here <<の単一の例外を除き、here-documentリダイレクトのhere-stringショートハンドを処理するほとんどすべてのシェルに当てはまりますyashyashbusyboxdash、他とash変異体は、ので、それらのシェルで本当にすべての後に両者の間にはほとんど違いがあるが、パイプでヒアドキュメントをバックアップする傾向があります。

わかりました-2つの例外。私はそれについて考えているので、ksh93実際にはのためにパイプをまったく行わず|、むしろビジネスw /ソケット全体を処理します- <<<*他のほとんどのように削除されたtmpファイルを実行します。さらに、パイプラインの個別のセクションをサブシェル環境に配置するだけです。これは、少なくともサブシェルのように動作するため、POSIXのup曲表現の一種です。したがって、フォークすらしません。

実際、ここでの@PSkocikのベンチマーク(これは非常に便利です)の結果多くの理由で大きく異なる可能性があり、これらのほとんどは実装に依存しています。ヒアドキュメントのセットアップの最大の要因は、ターゲットの${TMPDIR}ファイルシステムのタイプと現在のキャッシュ構成/可用性、さらに書き込まれるデータの量です。パイプの場合、必要なフォークのコピーが作成されるため、シェルプロセス自体のサイズになります。この方法でbashは、パイプラインのセットアップでひどいです$(コマンド)置換を含めるため -それは大きくて非常に遅いためですが、ksh93それによってほとんど違いはありません。

シェルがパイプラインのサブシェルを分割する方法を示す別の小さなシェルスニペットを次に示します。

pipe_who(){ echo "$$"; sh -c 'echo "$PPID"'; }
pipe_who
pipe_who | { pipe_who | cat /dev/fd/3 -; } 3<&0

32059  #bash's pid
32059  #sh's ppid
32059  #1st subshell's $$
32111  #1st subshell sh's ppid
32059  #2cd subshell's $$
32114  #2cd subshell sh's ppid

パイプラインpipe_who()コールが報告する内容と現在のシェルで実行されるレポートの違いは、(サブシェルが展開さ)れた$$ときに親シェルのpidを要求するという指定された動作によるものです。けれどもbashサブシェルは間違いなく別のプロセスがあり、$$特別なシェルパラメータは、この情報の信頼できる情報源ではありません。それでも、サブシェルの子shシェルは、その正確なレポートを拒否しません$PPID


非常に役立ちます。カーネル内のファイルシステムに名前はありますか?カーネル空間に存在するということですか?
utlamn

2
@utlamn-実際、はい-単にpipefs。これは、カーネル内のすべてのだ-しかし、(脇FUSEのようなものから)そうであるすべてのファイルI / Oが
mikeserv

10

ベンチマークに代わるものはありません。

pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do cat<<< foo >/dev/null; done  )

real    0m2.080s
user    0m0.738s
sys 0m1.439s
pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do echo foo |cat >/dev/null; done  )

real    0m4.432s
user    0m2.095s
sys 0m3.927s
$ time (for((i=0;i<1000;i++)); do cat <(echo foo) >/dev/null; done  )
real    0m3.380s
user    0m1.121s
sys 0m3.423s

そして、大量のデータの場合:

TENMEG=$(ruby -e 'puts "A"*(10*1024*1024)')
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do echo "$TENMEG" |cat >/dev/null; done  )

real    0m42.327s
user    0m38.591s
sys 0m4.226s
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do cat<<< "$TENMEG" >/dev/null; done  )

real    1m26.946s
user    1m23.116s
sys 0m3.681s
pskocik@ProBook:~ 

$ time (for((i=0;i<100;i++)); do cat <(echo "$TENMEG") >/dev/null; done  )

real    0m43.910s
user    0m40.178s
sys 0m4.119s

パイプバージョンのセットアップコストは大きくなりますが、最終的にはより効率的になります。


@mikeservそれは正しかった。大量のデータを含むベンチマークを追加しました。
PSkocik

2
echo foo >/dev/shm/1;cat /dev/shm/1 >/dev/null両方のケースで高速に見えた
...-user23013

@ user23013それは理にかなっています。なぜ効率が向上するのか、echo "$longstring"または<<<"$longstring"短い文字列では効率がそれほど重要ではないのかがわかりません。
PSkocik

私の場合(Ubuntu 14.04では、Intelクアッドコアi7)cat <(echo foo) >/dev/nullは、よりも高速ですecho foo | cat >/dev/null
パブーク

1
@Premええ、それはより良いアプローチですが、さらに良い方法はこれについてまったく心配せず、仕事に適切なツールを使用することです。ヒアドキュメントがパフォーマンス調整されると考える理由はありません。
PSkocik
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.