中スタックオーバーフローに対する回答、質問に記載されているいくつかの小さなタスクを実行するためのコード例を提供しました。元の質問は、どのスキルが最も速く実行されるかに関するものでした(したがって、ここではパフォーマンス基準が適用されます)。
別のコメント提示者/回答では、POSIX定義システムAPI呼び出し(この場合readdir
)を作成する代わりに提案しました。直接システムはカーネル(syscall(SYS_getdents,...)
)を呼び出し、パフォーマンスの差が25%の範囲にあると主張します。 (実装もベンチマークもしませんでした。実際にパフォーマンスが良くなると思います。)
私の質問は、提案されたシステムコールベースのソリューションのパフォーマンス特性についてです。なぜより速いかもしれません。パフォーマンスが良いいくつかの理由を考えてみましょう。
- POSIXは
readdir
本質的にsyscall(SYS_getdents,...)
/より複雑です。getdents()
readdir
(おそらく呼び出しはsyscall(SYS_getdents,...)
間接的なオーバーヘッドを追加するだけです。readdir
(カーネル呼び出しごとに)1つのレコードのみを返しますが、syscall(SYS_getdents,...)/
getdents()`は(おそらく)カーネル呼び出しごとに複数のレコードを返します。
上記の#1は本当とは想像できません。 glibcの実装は、directよりも「実際の」システムコールを呼び出すことができないほど似ていますreaddir
。getdents
readdir
syscall(SYS_getdents,...)
getdents()
readdir
また、呼び出しがラップされ、呼び出しgetdents
も可能であるため、#2は真であると想像することはできません(提案された答えは直接呼び出すのではなく具体的に使用されます)。 Linuxのglibcのすべては、おそらくこの場合#2になります。syscall(SYS_getdents,...)
getdents
syscall(SYS_getdents,...)
getdents
syscall(syscallid, args)
はい本当。
私の考えでは、最後の可能性が最高の説明です。カーネル呼び出しが少ないほどパフォーマンスが向上します。
「直接カーネル呼び出し」がPOSIX定義関数を呼び出すよりもはるかに速い理由の具体的な説明はありますか?
答え1
PLT
これがLinuxで最も高価な呼び出しの1つであることを考えると、間接参照や可変引数(レジスタはメモリに保存する必要があります)などの要因はsyscall()'s
ほとんど影響しません。getdents
私のコンピュータでは、空のディレクトリを完全に読み取るのに約5μsかかります。
fdopendir
+readdir
効果は、getdents
バッファ割り当て/使用可能(0.1μs)を追加し、stat
提供されたfdがディレクトリタイプ(0.4μs)であることを確認することです。readdir
次に、各ディレクトリエントリに対して簡単な呼び出しを実行します(バッファ内の1つの場所を移動して再入力できます)。
したがって、ワンタイムオーバーヘッドは0.5μsです。これは空のディレクトリのディレクトリスキャン時間の10%ですが、100エントリディレクトリの場合は1%にすぎず、大きなディレクトリではほとんど無視できます。 fdopenが必要ない場合、このオーバーヘッドは5倍になります(割り当て/無料コストのみ)。 (diropen
直接使用できない場合はfdopenのみが必要です(例: 'ted)、ファイルディスクリプタを別途入手する必要がありますopenat
。)
したがって、カスタムワンタイム割り当てバッファを使用するgetdents
場合空ディレクトリであり、大きなディレクトリではほとんど無視できます。
呼び出しの場合、readdir
PLT間接コストは最新のハードウェアでは通常1ns未満で、関数呼び出しのオーバーヘッドは約1〜2nsです。ディレクトリスキャン時間がマイクロ秒程度であることを考慮すると、readdir
これらの要素を単一のμsに変換するには少なくとも1000回の呼び出しが必要ですが、スキャンコストは340μsで、累積された1μsはそのうち約0.3%です。影響は次のとおりです。無視できる。これをインライン化するとreaddir
(したがってコールオーバーヘッドとPLTオーバーヘッドを削除する)、コード拡張にのみ役立ちますが、getdents
ボトルネックが発生するため、パフォーマンスは大幅に向上しません。
(追加のロックによりコストが高くなりますが、通常の呼び出しは通常スレッドセーフなので、readdir_r
必要はありません。readdir_r
readdir
〜しない限りあなたはそれらを呼び出すいくつかのスレッドを持っています同じディレクトリストリーム。 POSIXはまだこれを明示的に明らかにしていないかもしれませんが、glibcがもう使用されていないことを考えると、この保証はすぐに標準化されるべきだと思いますreaddir_r
。 )
答え2
そして友達のような機能はreaddir()
共有ライブラリlibcに実装されています。すべての共有ライブラリと同様に、共有ライブラリ内の関数のメモリアドレスを確認できるように、いくつかのリダイレクトが追加されます。
特定のライブラリ呼び出しが最初に実行されるとき、動的リンカはハッシュテーブル内でライブラリ呼び出しアドレスを見つける必要があります。これには少なくとも1回(おそらくそれ以上)の文字列比較が含まれます。これは比較的高価なアプローチです。見つかったアドレスはPLT
(プロシージャ接続テーブル)に格納され、次に関数が呼び出されたときに関数を見つけるためのオーバーヘッドが3つの命令に減ります(x86アーキテクチャでは他のアーキテクチャよりも少ない)。そのため、何かを共有オブジェクト(静的オブジェクトではなく)にコンパイルすると、わずかなオーバーヘッドが発生します。共有ライブラリのオーバーヘッドとLinuxで共有ライブラリがどのように機能するかについては、次を参照してください。トピックに関するUlrich Drepperの詳細な技術説明。
機能syscall()
自体は返品libcで実装されているので、リダイレクト機能もあります。ただし、その関数のみを使用し、他の関数は使用しないため、動的リンカーが実行する作業が少なくなります。また、特定の関数の実装は、例えば関数のreaddir
終了時に戻り値を変換し、エラーチェックなどを行う必要があります。これは追加のオーバーヘッドです。syscall()
直接実行されるプログラムはsyscall()
システムコールの直接戻り値を使用し、対応する変換は不要です(まだエラーチェックが必要なため、関数はかなり複雑になります)。
直接実行の欠点は、syscall()
移植性の低いAPIに切り替えることです。マンページでは、syscall()
libcが処理するいくつかのアーキテクチャ固有の制約について説明します。syscall()
これを直接使用すると、関数は作業中のアーキテクチャで実行できますが、ARMシステムでは失敗する可能性があります。
一般的に、私はsyscall()
アセンブリ言語で直接コードを書くことをお勧めしないのと同じ理由で、APIを直接使用しないことをお勧めします。はい、これにより速度が速くなりますが、メンテナンスの負担は大きくなります。あなたができることはいくつかあります:
- パフォーマンスには気にしないでください。システムはますます安価になっており、多くの場合、「パフォーマンスを向上させるためにプログラマに時間当たりの費用を支払う」よりも、「作業を高速化するために他のシステムを追加する」方が安い。
- 共有ライブラリを使用するのではなく、静的ライブラリ用にソフトウェアをコンパイルするなど、パフォーマンスに重要ないくつかの小さな点(例
gcc -static
:) - プロファイラを使用して、進行が遅い部分を確認して集中してください。それらシステムコールの方法を心配する代わりに。
答え3
「直接カーネル呼び出し」がはるかに速い理由の具体的な説明はありますか?
これが実際に重要であると考える悪名高い状況は、ディレクトリのサイズと与えられたファイルシステムがあらかじめ読み込みを十分に実行しない場合です。要求ごとのディスク装置待ち時間。つまり、使用量の多いディスク(長い要求キュー)やネットワーク経由でアクセスされるディスクでは、この数値が高くなる可能性があります。
http://be-n.com/spw/you-can-list-a-million-files-in-a-directory-but-not-with-ls.html
ほとんどの場合、glibcが使用するバッファリング量は問題を引き起こしません。通常、バッファリングされたコードをバイパスする理由で、これらの極端なケースを指摘すると、「急性の最適化」または同様の迷惑が発生する可能性があります。
https://github.com/BurntSushi/walkdir/issues/108
Linusの記事の1つを読むのに疲れていない場合は、ファイルシステムディレクトリの事前インポートに関するいくつかのコメントを読んでください。彼らはそれを公開することも公開しないかもしれません。
https://lore.kernel.org/lkml/[Eメール保護]/T/#u
ext4_readdir() は Linus の好言状を満たすために変更されていません。私はまた、彼が他のファイルシステムのreaddir()で使用したい方法を見ることができません。私はXFSがディレクトリに対して(物理的にインデックス付けされた)バッファキャッシュも使用していると思います。核兵器ディレクトリが断片化されている場合の先読み実装]。 bachefsはreaddir()のページキャッシュをまったく使用せず、独自のbtreeキャッシュを使用します。 btrfsに何かが落ちたかもしれません。