Stack Overflowでの回答 で、質問で参照されているいくつかの小さなタスクを実行するためのコードサンプルを提供しました。元の質問は、最もパフォーマンスの高い手法に関係していました(したがって、ここではパフォーマンス基準が機能します)。
別のコメント提供者/回答者は、POSIX定義を作成することを提案しました システム API呼び出し(この場合、readdir
)は、カーネルへのdirectシステムコールの実行ほど高速ではありませんでした(syscall(SYS_getdents,...)
)そして主張されたパフォーマンスの違いは25%の範囲にあります。 (私は実装して再ベンチマークしませんでした。実際にはパフォーマンスが向上する可能性があると思います。)
私の質問は、提案されたシステムコールベースのソリューションのパフォーマンス特性となぜより高速であるかについてです。パフォーマンスが向上する理由はいくつか考えられます。
readdir
は、本質的にsyscall(SYS_getdents,...)
/getdents()
よりも複雑です。readdir
(おそらくsyscall(SYS_getdents,...)
を呼び出すだけで間接参照のオーバーヘッドが追加されますreaddir
は(カーネル呼び出しごとに)1つのレコードのみを返しますが、syscall(SYS_getdents,...)/
getdents() `は(おそらく)カーネル呼び出しごとに複数のレコードを返します上記の#1が真実だとは想像できません。 readdir
とgetdents
は非常に似ているため、glibcでのreaddir
の実装では、syscall(SYS_getdents,...)
/getdents()
が呼び出されます。
readdir
を呼び出すとgetdents
がラップされる可能性があり、syscall(SYS_getdents,...)
もgetdents
を呼び出す可能性があるため、#2が真であるとは想像できません(提案された回答は、getdents
を直接呼び出す代わりにsyscall(SYS_getdents,...)
を具体的に使用します。Linux上のglibc内のすべてがsyscall(syscallid, args)
に要約される可能性があります。その場合、#2はおそらくistrue。
最後の可能性は、私にとって最良の説明のように思われます。カーネルへの呼び出しが少ないほど、パフォーマンスが向上するだけです。
「直接カーネル呼び出し」がPOSIX定義関数を呼び出すよりも測定可能なほど高速である理由について具体的な説明はありますか?
PLT
がLinuxで最も高価な呼び出しの1つであることを考えると、getdents
間接参照やsyscall()'s
可変個引数(レジスタをメモリに保存する必要がある)などの要素はほとんど役割を果たさないはずです。 。
私のマシンで空のディレクトリを完全に読み取るには、約5µs、100アイテムのディレクトリ37µs、1000アイテムのディレクトリ340µs、10,000アイテムのディレクトリ3.79msの費用がかかります。
fdopendir
+ readdir
がgetdents
の上で行うことは、バッファの割り当て/割り当て解除(0.1µs)を追加し、stat
が提供されたfdがディレクトリの種類(0.4µs)。次に、readdir
は、ディレクトリエントリごとに1つの安価な呼び出しを行います(バッファ内の位置を移動し、場合によっては補充します)。
したがって、0.5µsの1回限りのオーバーヘッドのようなものがあります。これは、空のディレクトリのディレクトリスキャン時間の10%ですが、100アイテムのディレクトリでは1%にすぎず、大きなディレクトリでは無視できるほど少ないです。 fdopenが必要ない場合、このオーバーヘッドは5分の1になります(割り当て/割り当て解除のコストのみ)。 (fdopenが必要なのは、直接diropen
できないため、個別に取得した(たとえば、openat
'ted)ファイル記述子を経由する必要がある場合のみです)。
したがって、カスタムの1回限り割り当てられたバッファをgetdents
と一緒に使用すると、emptyディレクトリのスキャンコストを2〜10%節約でき、大きなディレクトリでは無視できるほど少なくなります。
readdir
呼び出しの場合、最新のハードウェアでのPLT間接化のコストは、通常1ns未満であり、関数呼び出しのオーバーヘッドは約1〜2nsです。ディレクトリのスキャン時間がマイクロ秒のオーダーであるとすると、これらの要素を1つのµsに変換するには、少なくとも1000 readdir
の呼び出しを行う必要がありますが、スキャンコストは340µsであり、発生した1余分なµsはその0.3%のようなもので、影響はごくわずかです。これらのreaddir
をインライン化すると(したがって、呼び出しのオーバーヘッドとPLTのオーバーヘッドを排除する)、コードを拡張するだけで済みますが、getdents
がボトルネックであるため、パフォーマンスはそれほど向上しません。
(readdir_r
は追加のロックのために高価ですが、プレーンなreaddir
呼び出しは通常スレッドセーフであるため、readdir_r
は必要ありませんunlesssameディレクトリストリームで複数のスレッドを呼び出しています。POSIXはまだこれについて明示的ではないかもしれませんが、私は信じていますglibcがreaddir_r
を非推奨にするところまで進んだことを考えると、この保証はすぐに標準化されるはずです。)
readdir()
やfriendsのような関数は、共有ライブラリであるlibcに実装されています。すべての共有ライブラリと同様に、共有ライブラリ内の関数のメモリアドレスを解決できるようにするために、リダイレクトが追加されます。
特定のライブラリ呼び出しが初めて実行されるとき、ダイナミックリンカはハッシュテーブル内のライブラリ呼び出しのアドレスを検索する必要があります。これには、少なくとも1つ(場合によってはそれ以上)の文字列比較が含まれますが、これは比較的コストのかかる方法です。見つかったアドレスはPLT
(プロシージャリンケージテーブル)に保存されるため、次に関数が呼び出されたときに、関数を見つけるオーバーヘッドが3つの命令に削減されます(x86アーキテクチャでは、それよりも少なくなります)。他のいくつかのアーキテクチャで)。これが、(静的オブジェクトではなく)共有オブジェクトとして何かをコンパイルするときにオーバーヘッドが発生する理由です。共有ライブラリのオーバーヘッドとLinuxでの共有ライブラリの動作の詳細については、 lrich Drepperによるこの件に関する詳細な技術的説明 を参照してください。
syscall()
関数自体はalsolibcに実装されているため、リダイレクトもあります。ただし、その関数のみを使用する(他の関数は使用しない)ため、ダイナミックリンカーの作業は少なくなります。さらに、readdir
などの特定の関数の実装では、syscall()
関数の終了時に戻り値を変換し、エラーチェックなどを行う必要があります。これは余分なオーバーヘッドです。 syscall()
を直接実行するプログラムは、システムコールの直接の戻り値を処理し、その変換を必要としません(エラーチェックを行う必要があり、関数が大幅に複雑になります)。
syscall()
を直接実行することの欠点は、移植性の低いAPIに移行することです。 syscall()
マンページでは、libcが処理するアーキテクチャ固有の制約について説明しています。 syscall()
を直接使用する場合、関数は処理しているアーキテクチャで機能する可能性がありますが、たとえば、アームマシンでは失敗します。
一般に、アセンブリ言語でコードを直接記述しないことをお勧めするのとまったく同じ理由で、syscall()
APIを直接使用しないことをお勧めします。はい、それは最終的には速くなるかもしれませんが、メンテナンスの負担は(はるかに)高くなります。代わりにできることがいくつかあります。
gcc -static
)「直接カーネル呼び出し」が測定可能なほど高速になる理由について具体的な説明はありますか?
これが実際に問題になる悪名高いケースは、ディレクトリのサイズとディスクデバイスのリクエストごとのレイテンシを考慮して、ファイルシステムが十分な先読みを行わない場合だと思います。つまりこれは、ビジーディスク(長い要求キュー)、またはネットワーク経由でアクセスされるディスクで高くなる可能性があります。
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を読むのに飽き飽きしていなければ、ファイルシステムディレクトリの先読みについてコメントがありました。彼らは明らかにするかもしれないし、しないかもしれない。
https://lore.kernel.org/lkml/[email protected]/T/#
ext4_readdir()は、Linusの暴言を満たすように変更されていません。他のファイルシステムのreaddir()で使用されている彼の望ましいアプローチもわかりません。 XFSはディレクトリにも(物理的にインデックス付けされた)バッファキャッシュを使用すると思います[少なくとも、ディレクトリが断片化されている場合、コア先読み実装のメリットを享受できないことを意味します]。 bcachefsはreaddir()にページキャッシュをまったく使用しません。 btreeに独自のキャッシュを使用します。 btrfsに何かが足りない可能性があります。