> [!abstract] 概要(Abstract 日本語訳) > Linuxコミュニティはユーザがカーネルに機能を拡張するカスタムプログラムをロードできるeBPF技術の急速な発展を目撃してきた。eBPFを強力にする鍵となる機能はeBPFマップであり、これはeBPFプログラムにデータストレージと通信の能力を提供する。しかし、eBPFプログラムで広く使われているにもかかわらず、eBPFマップの性能はほとんど注目されてこなかった。eBPFマップの性能特性を理解するために、我々はそれらに対する包括的なベンチマークを実施した。ベンチマーク結果は、異なる種類のeBPFマップのアクセスオーバーヘッドを示し、アクセスオーバーヘッドに影響を与える様々な要因の影響を明らかにする。ベンチマーク結果を分析することで、eBPFユーザがeBPFマップをより効率的に使うためのいくつかの示唆を導出する。 ## 論文情報 - タイトル: Understanding Performance of eBPF Maps - 著者: [[Chang Liu]]([[Tsinghua University]])・[[Byungchul Tak]]([[Kyungpook National University]])・[[Long Wang]]([[Tsinghua University]] & [[Zhongguancun Laboratory]]) - 媒体: Workshop on eBPF and Kernel Extensions(eBPF '24)、ACM SIGCOMM 2024 併設ワークショップ、2024-08-04〜08、Sydney, NSW, Australia - 発表年: 2024 - DOI: [10.1145/3672197.3673430](https://doi.org/10.1145/3672197.3673430)、ACM ISBN 979-8-4007-0712-4/24/08 - コード URL: 記載なし(PDF本文中に言及なし) ## 概要 Linuxカーネルを安全に拡張するeBPFプログラムの中核的な機能であるeBPFマップ(データ保存・ユーザ空間との通信手段)について、その性能特性がこれまでほとんど調べられてこなかったという課題に対し、著者らは配列・ハッシュ・per-cpu変種・リングバッファ・perfバッファ・キュー・スタックの各マップ型を対象に自動化されたベンチマークシステムを構築し、包括的な性能測定を行った。結果として、メモリフットプリントとキャッシュのホット性が主要な決定要因であることを見出し、それに基づくeBPFマップの効率的な使い方の示唆を導出した。 ## 問題設定 - **入力**: 各種eBPFマップ型(array・per-cpu array・hash・per-cpu hash・ring buffer・perf buffer・queue・stack)と、マップサイズ・キーサイズ・値サイズ・操作種別(lookup/update/push/pop/output/submit等)の組み合わせからなるベンチマーク設定。 - **出力**: 各設定に対応するeBPFプログラムの実行時間(ns単位)、ホットキャッシュ/コールドキャッシュ両条件での測定値。 - **前提条件**: Linux 6.0.1カーネル、x86-64、JIT有効化(eBPFネイティブコードは対象外)。eBPFはLinuxとWindows双方、汎用CPUとSmartNIC双方で利用可能だが、本研究はLinuxカーネル内・x86-64・JIT有効という最も一般的なデプロイ形態に限定する。ハッシュマップは事前確保(pre-allocated)されたものを使用。 ## 提案手法 - **アーキテクチャ**: ベンチマークシステムはコード生成器(code generator)と`bpf_bench()`システムコールの2部から構成される([[#Figure 1|Figure 1]])。コード生成器はユーザ指定のベンチマーク設定(マップ型・マップサイズ・値サイズ・操作)を読み取り、(i)被験対象のeBPFプログラム、(ii)eBPFローダプログラム、(iii)ベンチマークプログラムの3つを生成する。ローダプログラムは生成されたeBPFプログラムをカーネルにロードし、フック点として実装した空のカーネル関数(`kprobe/empty_kernel_func`)にアタッチする。ユーザ空間のベンチマークプログラムが`bpf_bench()`システムコールを1回呼び出すと、そのループ内で空のカーネル関数を連続的に呼び出し、その都度eBPFプログラムを1回実行させる。 - **アルゴリズム/手法の詳細**: - Linuxネイティブの`bpf()`システムコールによるベンチマーク手法では、eBPFプログラムを多数回実行しその合計実行時間を測るだけなので、常にホットキャッシュ下での実行になり、キャッシュのホット性の影響や並行実行時のオーバーヘッドを測定できない。この制約を解消するため`bpf_bench()`という専用システムコールを実装した。 - **ホットキャッシュ測定**: `bpf_bench()`が空のカーネル関数を連続的に呼び出す。 - **コールドキャッシュ測定**: `bpf_bench()`がループ内で空のカーネル関数を実行するたびにx86命令`wbinvd`を実行してCPUキャッシュを明示的にフラッシュする。 - **並行実行**: 複数CPUコア上で`bpf_bench()`を同時に呼び出すことで、並行実行のオーバーヘッドを計測できる(ただし本論文では紙幅の都合により並行シナリオの結果自体は割愛)。 - **ベースライン**: 何も処理をしない空のeBPFプログラム(null eBPFプログラム)の実行オーバーヘッドを測定し、これをトランポリンコードのオーバーヘッドのベースラインとして、各マップ操作の測定値から差し引くことでマップアクセス自体の純粋なオーバーヘッドを得る。 - **大きな値サイズへの対応**: eBPFのスタックは512バイトしかなく、512バイトを超える値を直接eBPFスタック上の変数として定義できない。この制約を回避するため、値のメモリを別のarrayマップ(`value_map`)に確保し、その値のメモリ領域へのポインタをそのarrayマップから取得してから使う設計にした([[#Figure 2|Figure 2]])。この設計により、arrayマップから値を取得する追加オーバーヘッドを理解する必要が生じた。 - **実装上の工夫**: コード生成器はベンチマーク設定に基づいて、読み取り操作(lookup on array、pop on stack/queue)については対応するeBPFヘルパー関数呼び出し自体のオーバーヘッドを測定し、書き込み操作(update on array、output on ring/perf buffer、push on stack/queue)については、値をマップから取得する処理(①)と値を代入する処理(②)を書き込み前の前処理として追加で測定した上で、マップへのアクセス処理(③)を測定する([[#Figure 2|Figure 2]])。`bpf_bench()`は増分値`i`をeBPFプログラムに渡し、これを使って毎回異なるマップ要素へのアクセスを生成する。 ## 新規性 Linux ネイティブの`bpf()`システムコールによるeBPFベンチマーク手法は、常にホットキャッシュ下での実行になるためキャッシュホット性の影響を測定できず、また並行実行時のオーバーヘッドも測定できない。本研究はこの2つの制約――**キャッシュホット性の制御**(`wbinvd`によるコールドキャッシュ再現)と**並行アクセスの計測**(複数CPUコアでの`bpf_bench()`同時呼び出し)――を解消する自動化ベンチマークシステムを新たに構築した。加えて、eBPFのカーネルパッチ・カーネルドキュメント・チュートリアルの多くが性能情報を欠いているという実務上の空白に対し、eBPFマップという最も基礎的なデータ構造層の網羅的な性能特性(メモリフットプリント依存性・キャッシュホット性依存性)を初めて体系的に整理した点が新規性である。 ## 実験設定 - **実験環境**: Intel Xeon Silver 4210R × 2ソケット(ベース周波数2.40GHz、物理コア10個/CPU)。L1i/L1dキャッシュ各32KB、L2キャッシュ1MB、L3キャッシュ13.75MB。128GB DDR4メモリ(3200MHz)。動的P-state/C-stateを無効化しベース周波数固定で測定安定性を確保。Linux 6.0.1カーネル。 - **データセット/ワークロード**: 合成ベンチマーク設定(Table 1に列挙、7マップ型×操作の組み合わせ)に加え、MySQLサービスへのsysbench OLTPリードクエリを用いた実workload下の測定(システムコールトレーシング応用)。 - **比較対象**: なし(他システムとの比較ではなく、マップ型間・設定パラメータ間の比較が中心)。ベースラインとして「空のeBPFプログラム」のオーバーヘッドを用いる。 - **評価指標**: eBPFプログラムの実行時間(ナノ秒)。1つの設定につき1024個の異なるエントリへアクセスして平均値を算出し、各設定で5回の実験を実施。 ## 実験結果 - **Array / Per-cpu Array**([[#Figure 3|Figure 3]]-a〜d): arrayマップの要素は連続したメモリ領域に格納されるため、eBPF JITコンパイラがヘルパー関数呼び出しを数命令のアドレス計算にインライン化できる。そのためlookupオーバーヘッドはマップサイズにも値サイズにも依存せず一定。ホットキャッシュ下ではこのインライン化されたアドレス計算オーバーヘッドは無視できるほど小さく(null eBPFプログラムのオーバーヘッド線とほぼ重なる)、コールドキャッシュ下では約100nsの追加オーバーヘッドが生じる(命令をI-キャッシュへロードするオーバーヘッド)。per-cpu arrayのlookupはarrayよりわずかに高いオーバーヘッドを示す(現在のCPU IDを取得する処理が必要でインライン化を妨げるため)。array/per-cpu arrayのupdate操作は値サイズが大きくなるほどオーバーヘッドが増加する(メモリコピーオーバーヘッドの増加のため自然な結果)。 - **Hash / Per-cpu Hash**([[#Figure 3|Figure 3]]-e〜j): 1024エントリのhashマップへのアクセスにおいて、(i)キーサイズが大きいほどlookup/updateオーバーヘッドが増加する(大きなキーのハッシュ値計算コストが高いため)、(ii)マップサイズが大きいほどlookup/updateオーバーヘッドが減少する(バケット数がマップサイズを2の冪に切り上げて決まるため、マップサイズが大きいほどバケット数が増えハッシュ衝突が減る)。per-cpu hashも同様の傾向を示すが、update操作のオーバーヘッドは値サイズの増加とともに急激に増加する。ソースコード精査の結果、これは新規キー挿入時に対象キーに対応する全CPU分の値領域を確保し、現在のCPU以外の値をすべて0で初期化するためであることが判明した。この特性から、per-cpu hashマップは新規キー挿入が頻繁で値サイズが大きい状況では使うべきでないと示唆される。 - **Ring Buffer / Perf Buffer**([[#Figure 3|Figure 3]]-k〜l): output操作ではring bufferの方がperf bufferより性能が良い。ring bufferはreserve+submitという新しい書き込み方式を提供し(reserveでリングバッファ内の予約済みメモリ領域へのポインタを取得し直接書き込んだ後submitで送信準備完了を通知)、メモリコピーを削減することでさらに良い性能を達成する。 - **Queue / Stack**([[#Figure 3|Figure 3]]-m〜p): push操作とpop操作のオーバーヘッドは同程度で、マップサイズのみが影響要因。ホットキャッシュかつ値サイズが大きい場合、peek操作はpop操作より小さいオーバーヘッドを示す(peekは同じ要素に繰り返しアクセスするためCPUキャッシュに残り、コピーオーバーヘッドが小さくなるため)。 - **メモリアクセスオーバーヘッドの総括**: 値サイズが増えるほど(hashマップではキーサイズも)、またring bufferのreserve+submit方式による削減効果を含め、メモリアクセスオーバーヘッドが増加する。ただしこれが顕著になるのは1KB・4KBのような大きなメモリアクセス時のみで、多くのユースケース(値サイズ256バイト未満)では意図的な最適化は不要と結論づけている。値サイズが256バイト未満の領域では、コールドキャッシュ下のeBPFマップアクセスオーバーヘッドはホットキャッシュ下より1桁高い。 - **システムコールトレーシング実験**(MySQL実workload、[[#Table 2|Table 2]]): `pread64()`・`read()`・`write()`をそれぞれ単独でトレースした場合と3つ同時にトレースした場合を比較した4実験(各5秒間)を実施したところ、eBPFプログラムがより頻繁に実行されるほど平均実行時間が短くなった。3syscall同時アタッチ時の実行時間は、単一syscallアタッチ時の35%〜84%にとどまる。これは実行頻度が高いほど2回のeBPF実行間に他のコードが実行される機会が減り、フラッシュされるeBPFコード/データのキャッシュラインが少なくなるためであり、著者らはこれを「volume discount」特性と呼ぶ。 **Table 1: eBPFマップベンチマークの設定一覧**(値サイズ属性は全設定に含むため列から省略) | マップ型 | マップ属性 | 操作 | 結果(Figure 3) | |---|---|---|---| | array | — | lookup | 3-a | | array | — | update | 3-b | | per-cpu array | map size | lookup | 3-c | | per-cpu array | map size | update | 3-d | | hash | key size | lookup | 3-e | | hash | map size | lookup | 3-f | | hash | key size | update | 3-g | | hash | map size | update | 3-h | | per-cpu hash | map/key size | lookup | 3-i | | per-cpu hash | map/key size | update | 3-j | | ring buffer | map size | output | 3-k | | perf buffer | map size | output | 3-l | | queue | — | push | 3-m | | queue | — | pop/peak | 3-n | | stack | map size | push | 3-o | | stack | map size | pop/peak | 3-p | **Table 2: システムコールトレーシング実験の結果** | トレース対象syscall | 実行回数 | 合計時間(ns) | 平均時間(ns) | |---|---|---|---| | pread64() | 6,784 | 2,032,298 | 299.6 | | read() | 84,782 | 10,665,819 | 125.8 | | write() | 29,069 | 6,341,248 | 218.1 | | 3syscall同時 | 123,248 | 13,069,100 | 1,061* | *3syscall同時の平均時間は他の値より小さく見えるが、この時間はカーネルのeBPF統計情報由来であり、eBPFプログラム自体のオーバーヘッドのみを含みトランポリンコードのオーバーヘッドを含まないため、Figure 3のnull eBPFプログラムオーバーヘッド(ホットキャッシュ)より低い値になり得ると論文は注記している。 **Figure 1: システム概観** ![[_attachments/2026_Unknown_Understanding_Performance_eBPF_Maps/fig01-system-overview.png]] (Figure 1. コード生成器がベンチマーク設定を読み取りeBPFプログラム・ローダプログラム・ベンチマークプログラムを生成し、ローダが`bpf()`システムコールでeBPFプログラムをカーネルにロード・空のカーネル関数へアタッチした後、ベンチマークプログラムが`bpf_bench()`システムコールで空のカーネル関数を実行してeBPFプログラムをトリガし実行時間を測定する一連の流れを示す。Source: Figure 1(論文本文の図をそのまま引用)。) **Figure 2: 生成されたeBPFマップベンチマークプログラムの例** ![[_attachments/2026_Unknown_Understanding_Performance_eBPF_Maps/fig02-generated-program-example.png]] (Figure 2. マップ型ARRAY・マップサイズ4096・値サイズ1024バイト・操作lookup/updateというベンチマーク設定から生成される、被験対象マップ(`bench_map`)と値保管用マップ(`value_map`)の定義、およびlookup/updateそれぞれのベンチマークプログラム本体を示す。色分けは設定項目と生成コード片の対応を表す。①値をマップから取得(update限定)、②値を代入(update限定)、③マップへアクセス。Source: Figure 2(論文本文の図をそのまま引用)。) **Figure 3: 全16設定のベンチマーク結果** ![[_attachments/2026_Unknown_Understanding_Performance_eBPF_Maps/fig03-benchmark-results.png]] (Figure 3(a〜p). Table 1の各設定に対応する16パネルの結果。横軸は値サイズ(バイト、1〜4096)、縦軸は実行時間(ns、パネルごとにスケール不同)。各パネルにホットキャッシュ(実線)/コールドキャッシュ(破線)、null eBPFプログラムオーバーヘッド(ホット/コールド)の基準線を重ねて表示。1024エントリへのアクセスを1024回試行した平均値を、各設定5回の実験で算出。Source: Figure 3(論文本文の図をそのまま引用、複数図表を1枚に結合)。) ## 考察 - 論文はeBPFマップの性能を左右する要因を「メモリフットプリント」と「キャッシュホット性」の2軸に集約して整理しており、この2軸は多くのマップ型・操作に共通して現れる説明変数として機能する。特にコールドキャッシュ下のオーバーヘッドがホットキャッシュ下より1桁高いという知見は、実運用のeBPFプログラム(完全なホット/コールドではなく混在するキャッシュライン下で動く)を理解する上での上下限として位置づけられている。 - 「volume discount」特性は、トレーシングアプリケーション開発者にとって「フック数とオーバーヘッドのトレードオフ」を考える際の実践的な指針になる、と著者らは位置づけている。 - per-cpu hashマップの新規キー挿入時のゼロ初期化コストは、ソースコード精査によって導かれた説明であり、ベンチマーク結果だけからは自明でない挙動(「4バイトの書き込みと256バイトの書き込みのオーバーヘッドがほぼ同じ」という直感に反する結果を含む)を丁寧に裏付けている。 ## 強み / 弱点・課題 - **強み**: (1) Linuxネイティブの`bpf()`ベンチマーク手法が測定できないキャッシュホット性・並行実行の影響を測定可能にする独自のベンチマークシステム(`bpf_bench()`)を設計・実装した。(2) 配列・ハッシュ・per-cpu変種・リングバッファ・perfバッファ・キュー・スタックという主要なeBPFマップ型を単一の自動化フレームワークで網羅的にベンチマークした。(3) 合成ベンチマークに加えMySQL実workloadでの検証(volume discount特性)により、理論的知見と実践的知見の両方を提供する。 - **弱点・課題**(論文自身が明記する限定事項): (1) 測定結果の絶対値(実行時間)は使用したハードウェア(CPU周波数・メモリ周波数・キャッシュサイズ)に強く依存し、他のハードウェア構成へは一般化できない(導かれた結論・傾向自体は一般的と主張)。(2) 測定はLinux 6.0.1カーネルに固定されており、eBPF実装が急速に進化しているため他のカーネルバージョンでは異なる結果になり得る。(3) 本研究はLinuxカーネル内・x86-64・JIT有効という設定に限定しており、Windows版eBPF・SmartNICオフロード・eBPFネイティブコード・ユーザ空間ランタイム上のeBPFは対象外。(4) 並行アクセスシナリオの結果は紙幅の制約により本論文からは割愛されている(future workとして言及)。(5) ユーザ空間からのeBPFマップアクセスのベンチマークも将来課題として残されている。