
2026/07/29 0:55
eBPF コードのプロファイリング方法は?
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
核心メッセージは、Linux カーネルの LSM フレームワーク内の最適化されていない eBPF セキュリティフックがファイルを開く総時間のほぼ 90% を消費し、システム管理者およびセキュリティエンジニアにとって重大なパフォーマンスボトルネックを引き起こすという点です。これを証明するために、研究者たちは低オーバヘッドの C ラインを用いたハネスを使って生 Syscall を呼び出し、カスタム
perf パスと JIT コンパイルを含む正確なカーネル機器計測を用いて、厳密なテスト手法を開発し、open などの操作における eBPF テールコールによって引き起こされる遅延を特定しました。このデータはパフォーマンスチューニングが必須のプロダクション環境において重要であり、介入なしにセキュリティの可観測性機能が直接ファイル操作速度を妨害します。今後、開発者はこれらのホットパスを対象としたキャッシュまたはアルゴリズム上の改善を優先して lag を削減する必要があります。最終的には、この研究は eBPF をカーネル強化のために導入する際には、セキュリティ上の利得がシステムの利用可能性および効率性の受容できないコストによって台無しにならないよう、テールコールチェーンの慎重な最適化が必要であることを示しています。本文
eBPF ワークロードのパフォーマンス測定手法:ファイルオープンフックによるオーバーヘッド分析
本記事では、eBPF コードを実行する際のパフォーマンスへの影響(オーバーヘッド)を測定するための具体的な手順と手法について解説します。特に OS の核心機能である「ファイルオープン操作」における eBPF フックによるコスト検証を例に挙げます。
目的:簡潔なテスト用アプリケーションの作成
eBPF を使用してファイルオープンフックを追加した際のパフォーマンスオーバーヘッドを測定するためには、依存関係を最小限に抑えた簡易な C テストプログラムが必要です。本稿で用意するコードは、キャッシュが温まった状態(Warm Cache)で同一ファイルを繰り返しオープンし、ファイルシステムやディスク I/O によるノイズを排除した上で、p50/p99 の値を精度よく推定することを目的としています。
ベンチマーク用 C コードの概要
以下のコードは libc の
openat() ラッパーではなく、syscall(SYS_openat, …) を直接呼び出すことで、ライブラリ関数のオーバーヘッドまで含めた純粋なシステムコールコストを測定します。また、メモリ割り当て時に行われるページフォールトを防ぐため、初期にメモリアリーヤを確保し全アクセスすることで温め(Pre-warm)ています。
#define _GNU_SOURCE #include <fcntl.h> #include <stdio.h> #include <time.h> #include <stdint.h> #include <stdlib.h> #include <unistd.h> #include <sched.h> #include <sys/mman.h> #include <sys/syscall.h> static inline uint64_t now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); /* VDSO を経由するため、システムコールは発生しない */ return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec; } int main(int argc, char **argv) { const char *path = argv[1]; uint64_t n = strtoull(argv[2], NULL, 10); uint64_t warm = n / 10; /* メモリ領域を事前確保し、ページフォールトを発生させてからロックする */ uint32_t *d = mmap(NULL, n * sizeof(uint32_t), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE, -1, 0); mlock(d, n * sizeof(uint32_t)); for (uint64_t i = 0; i < n; i++) d[i] = 0; /* メモリを全てアクセスしてフォールトを引き起こす */ for (uint64_t i = 0; i < n; i++) { /* ウォームアップ期間として最初の 10%の結果は破棄します。 */ uint64_t t0 = now_ns(); long fd = syscall(SYS_openat, AT_FDCWD, path, O_RDONLY); /* libc のラッパーを使用せず、直接システムコールを呼ぶ */ uint64_t t1 = now_ns(); if (fd >= 0) close(fd); d[i] = (uint32_t)(t1 - t0); } /* ループ終了後にのみ結果を出力する */ for (uint64_t i = warm; i < n; i++) printf("%u\n", d[i]); return 0; }
セットアップ:eBPF プロファイリング環境の準備
eBPF コードのプロファイリングを行う際は、
ツールがシンボルを解決し、問題箇所が自コードのどこにあるかを分析できるようにする必要があります。perf
1. JIT コンパイルとシンボル公開の有効化
以下のコマンドを実行することで、JIT コンパイルを有効化し、JIT によりコンパイルされた BPF シンボルを
/proc/kallsyms に公開します。これにより perf report がプログラム名や関数名を直接表示できるようになります(未知のアドレスではなく)。
sudo sysctl -w net.core.bpf_jit_enable=1 sudo sysctl -w net.core.bpf_jit_kallsyms=1
2. シンボルの確認
eBPF コードを実行した後、シンボルが正しく読み込めているかを確認します。LSM フックを測定しているため「lsm」を検出できるはずです。
# LSM フックの公開状況確認 sudo bpftool prog show | rg -A4 ' lsm ' # シンボルの公開状況確認(カーネルバージョンに合わせてカスタムパスを使用) sudo rg 'bpf_prog_[0-9a-f]+_ '/proc/kallsyms | rg 'security|path|file|open'
3. 環境への適応(カスタムツール版の場合)
標準パッケージに含まれていない場合や、特定のカーネルバージョン向けにビルドされた
perf を使用する場合、パスを明示的に設定します。本稿の例では以下の場所を使用しています。
PERF=/usr/lib/linux-tools/6.8.0-134-generic/perf
測定の実施:ベースラインと eBPF アタッチ後の比較
必要なツールが用意できたら、まずは eBPF コードを実行しない状態での**基準(ベースライン)**を測定し、その後 eBPF フックをアタッチした状態での測定を行います。
ベースライン測定
eBPF を使用しない状態で
/etc/hostname ファイルを開く操作を繰り返し、p50/p99 を計算します。この際、CPU コアの固定と高優先度の実行を設定することで、ノイズを低減し正確なベンチマークを取ります。
sudo taskset -c 3 chrt -f 99 ./bench /etc/hostname 100000 > /tmp/samples.txt
: 実行を CPU コア 3 に固定し、CPU 間遷移によるノイズを低減します。taskset -c 3
: ベンチマークに極めて高い優先度を付与します。通常のプログラムのほとんどよりも先に実行され、完了するかブロックされるか中断されるまで継続的に動作します。chrt -f 99- 結果: C コードでは最初の 10%(ウォームアップ期間)の結果は破棄されているため、出力ファイル
には 90,000 サンプルのデータが含まれます。/tmp/samples.txt
eBPF アタッチ後の測定
eBPF コードを実行中(またはフックをアタッチした状態)でも同様に測定します。今回は
perf record を使用してコールスタックを記録し、内核モードにおける CPU クロックスイクルのみをサンプリングします。
sudo "$PERF" record \ -g \ # コールスタックの記録 --call-graph fp # フレームポインターを用いてスタックを展開 -e cycles:k # ユーザー空間以外(システムコール、VFS、LSM、eBPF 実行など)の CPU クロックスイクルのみサンプリング -F 997 # 1 秒間に 997 サンプル(非整数値により周期的なバイアスを回避) -o ~/perf.data # 出力ファイル -- \ taskset -c 3 chrt -f 99 ./bench /etc/hostname 200000 \ > /tmp/samples.txt # ベンチマーク結果も同時に出力
データの解析
取得したデータをソートして表示し、パフォーマンスボトルネックを特定します。
sudo "$PERF" report -i ~/perf.data --stdio --sort comm,dso,symbol > perf.txt
フレイムグラフ解析と最適化の方向性
perf.data に対して Inferno を用いて生成されたフレイムグラフから、以下のコールスタックがパフォーマンスのボトルネックとなっていることが確認できます。
: eBPF LSM フック自体の実行コストbpf_lsm_file_open- テイルコール(Tail Call)系列:
: 89.52%do_dentry_open
: 89.30%0xffffffffc0288c18
: 87.40%bpf_prog_b06f413955402a4b_tail_call_security_check
: 77.57%bpf_prog_934361d723613c1c_enforce_access_policy
: 57.87%bpf_prog_a0f18f4b0b140d77_path_check_callback- ...および下位の処理 (
,bpf_probe_read_kernel
など)copy_from_kernel_nofault
考察と最適化方針
本記事の焦点は特定の数値そのものではなく、プロファイリング手法そのものにあります。観測されるオーバーヘッドは、フックが具体的にどのような処理を行うかに大きく依存するためです。
上記の結果から時間を費やす箇所を分析した結果、以下のような対策を検討できます。
- eBPF コードの最適化:
を用いて、eBPF が動作している際の C コード(または BPF コード)のパフォーマンス向上を図ります。perf-tune - アルゴリズムの改善: キャッシングの実装や、問題箇所に基づいたロジックの見直しを行います。
これにより、eBPF コードによるパフォーマンスへの影響を明確に把握し、必要な最適化の箇所を特定することが可能です。