Back to Timetable
When Goroutines Are Not Enough: Runtime Locality in High-Throughput Go
Goでは、goroutineによってOS threadやCPU coreをほとんど意識せずに並行処理を書けます。この実行場所を隠す抽象化は、Goの大きな強みです。 一方で、通信トラヒック、メトリクス、ログ、message queueのような高スループットなデータストリーム処理では、どのデータをどのqueueで受け、どのgoroutineが読み、どのCPU coreで実行されるかが性能差として現れることがあります。goroutineは並行性を表現する単位ですが、実行場所やローカリティを保証する単位ではないためです。 本発表では、自作のpacket capture toolを題材に、高スループットが求められるGoプログラムを改善していく過程を紹介します。このツールでは、Linuxのper-CPU BPF ring bufferからpacket eventを読み出します。BPF側ではCPUごとにqueueが分かれ、Go側ではreader goroutineが各queueを読みます。この構造では、reader goroutineを増やすだけでは性能が伸びず、queue、goroutine、OS thread、CPU coreの対応関係が性能に効くようになりました。 また、高負荷時にはpprof上でruntime.asyncPreemptが大きく目立つようになりました。本発表では、これを単にGo runtimeの遅さとして読むのではなく、hot loop、非同期プリエンプション、scheduler、CPU profileの見え方としてどう解釈するかを整理します。 この事例から持ち帰れるのは、runtime.LockOSThread()やCPU affinityの使い方そのものではありません。Goの抽象を通常の設計の前提としながら、どの条件で必要最小限だけ下の層を見るべきかを判断するための考え方です。pprofに現れるruntime関数の読み方や、データの分割単位がqueue、goroutine、CPU coreへどう対応しているかを、実測に基づいた学びを提供します。 ## 想定している話の流れ 0-4分: 問いの提示 goroutineは並行性の単位だが、ローカリティの単位ではない 4-8分: 普通のGo設計 flow、shard、partition、queueをgoroutineへ分散する設計 8-13分: 自作ツールの構造 per-CPU BPF ring bufferとreader goroutineの対応関係 13-20分: 最初の限界 reader goroutineを増やすだけでは性能が伸びない理由を、drop、latency、pprofから見る 20-27分: pprofとruntime.asyncPreemptの読み方 runtime関数が見えたときに、原因と現象をどう切り分けるか 27-35分: 実行場所を制御する LockOSThread、CPU affinity、same-CPU reader、split-coreの効果と副作用 35-40分: 一般化 metrics、logs、message queueなどのsharded consumerへ応用する ## 想定している聴講者 - Goで高スループットなサーバーやデータ処理基盤を書いているエンジニア - flow、shard、partition単位でgoroutineへ処理を分散している人 - pprofでruntimeやschedulerまわりの関数が目立ったとき、解釈に悩んだことがある人 - Go runtimeの抽象とOS scheduler、CPU coreの境界に興味がある人
Takeru Hayasaka Takeru Hayasaka

BBSakura Networks.Inc, Senior Software Engineer

Seconded from Sakura Internet to BBSakura Networks Inc., currently working on developing a dataplane for mobile networks using eBPF.