at SAKURA internet Inc. ◦ Currently on secondment to BBSakura Networks.Inc • Develops and operates mobile core ◦ Previously wrote game console firmware at a game company :-) • Co-Organizer of the eBPF Japan Meetup • Go Conference 2026 Staff ◦ 昨年のGoCon2025では「Goで体感するMultipath TCP ― Go 1.24 時代の MPTCP Listener を理解する」 という話で登壇させてもらってました🐈 2
streaming data pipelines that make efficient use of I/O and multiple CPUs.” cf. Go Concurrency Patterns: Pipelines and cancellation, Sameer, 2014 「Goの並行処理の仕組みで、I/Oや複数のCPUを効率よく利用するストリーム 処理を構成できる」 => その基本形を見てみよう! 6
eBPF packetを取得する packet packetの 情報 書く側: writer Go App eventを読み出す 読む側: reader pcap file eBPFから渡されるpacketの情報を、以降「event」と呼びます logo license: eBPF and the eBPF logo are trademarks of The Linux Foundation. The Go Logo is a trademark of Google LLC. 16
-> Go App の流れ Kernel 側 この場合のkey は5Tuple等で NIC Queueに振 り分けて仕事を 分割している NIC: RSS IP/portなどをhash Kernel 側 rx nic queue A CPU 0 で動く eBPF rx nic queue B CPU 1 で動く eBPF event 受け渡し用の queue User 側 BPF ring buffer eBPF eventを書く packet event event event Go App (reader goroutine) pcap file 19
側 ring = eventを渡す queue User 側 ring[0] CPU 0 で動くeBPF e e e Reader 0 goroutine packet pcap file ring[1] CPU 1 で動くeBPF packet e e e Reader 1 goroutine pcap file CPUの番号で、eBPFが書くringを選びます。 readerの番号は読むringとの対応であり、実⾏するCPUの指定ではありません。 20
12.53 s flatはその関数⾃⾝にかかったCPU時間、cumはその呼び出し先も含むCPU時間 関数名(flat上位5⾏) flat flat% cum cum% runtime.futex 7.44 s 59.38% 7.44 s 59.38% runtime.asyncPreempt 2.32 s 18.52% 2.32 s 18.52% runtime.memmove 2.18 s 17.40% 2.18 s 17.40% internal/runtime/syscall.Syscall6 0.21 s 1.68% 0.21 s 1.68% …/capture.(*Reader).RunShards.func1 0.11 s 0.88% 4.97 s 39.66% 5⾏⽬の処理は各ringを読むgoroutine。cum が妙に大きい……? 30
User 側 ring[0] CPU 0 で動くeBPF e e e packet Reader 0 goroutine CPU 0 pcap file ring[1] CPU 1 で動くeBPF packet e e e Reader 1 goroutine CPU1 pcap file 仮説3: 同じringを書く側と読む側のCPU配置分離で、 読み出せる量が変わるのでは? 34
• eBPF が動く CPU Core と、その ring を読む goroutine が動く CPU Core は 同一の方がいいのか? それとも異なる CPU Core で動いた方がいいのか? Writer Reader CPU N 書く 読む 実⾏CPUは 未指定 Ring N eBPF 共有メモリ Reader N reader N の N は「担当するring」の番号 54
CPU K CPU N Reader Writer/eBPF Ring N Reader Writer/eBPF Ring N CPU間で共有データの受け渡しを減らせる CPU 時間の取り合いにならない 書く・読む処理はCPU時間の取り合いになる CPU 間での共有データ受け渡しが多くなる どちらがいいか実際に計測して確認してみよう! ※今回はcacheの寄与は未計測。ちなみに別の論理CPUでもSMTでは実⾏資源を共有自体はする。 55
same / split の設定を加え比較を実施した D same-CPU: 16Core E split-CPU: 8 + 8 Core Reader Writer/eBPF Ring [16] CPU 8-15 CPU 0-7 CPU 0-15 Reader Writer/eBPF Ring [8] 受信の並列度を減らす代わりに、readerへ別の物理コアとして占有 割り当てをするのが変更ポイント 56
reader G Go:どのMでGを動かすか OS:どのCPUでMを動かすか ① LockOSThread ② CPU affinity 現在のM 指定したCPU N // LockOSThread後、同じ専用reader goroutineで続ける var set unix.CPUSet set.Set(cpu) if err := unix.SchedSetaffinity(0, &set); err != nil { return err } return readRing(ctx, ring) 58
CPU Core で動かすべきか 同じ⼊⼒で処理量・応答時間・両 thread の CPU使⽤量を比較することで最適な実行場所がわかる • liburing / io_uring_sqpoll(7)のmanにも「CPUに余裕がないと、polling threadとアプリがCPUを 取り合い、性能が落ち得る」という話が書いてある The polling thread consumes CPU. If the system is already CPU-bound, adding a polling thread may compete with the application for CPU resources, reducing overall performance. • 同じ論理CPUに寄せる/ same CPU N 別の論理CPUへ分ける / split CPU K CPU N proxy thread 依頼を書く SQPOLL Thread SQ: Submission Queue 共有メモリ proxy thread 依頼を書く SQPOLL Thread SQ: Submission Queue 共有メモリ 同じCPUから共有SQを読み書き CPU 時間の奪い合いにならない CPU時間の取り合いになる SQの受け渡しがコアをまたぐ 72