Skip to content
Published on

低レイテンシ取引システムのためのカーネルチューニング — マイクロ秒との戦争

Authors

はじめに

多くのサービスでは、100マイクロ秒の遅延は測定すらしない誤差の範囲です。しかし高頻度取引(HFT)やマーケットメイキングの世界では、100マイクロ秒は「注文が約定するか、他人の約定を眺めるか」を分ける時間です。取引所のマッチングエンジンに先に届いた注文が有利な価格を取り、遅れた注文は不利になった気配を受け取ります。このドメインにおいてレイテンシは抽象的な品質指標ではなく、損益計算書に直接記録される数字です。

本記事は低レイテンシ取引システムの観点からLinuxカーネルチューニングを整理したものです。先に断っておくと、ここに出てくる技法の大半は一般のサービスには過剰です。記事の後半で「なぜあなたのWebサーバーに適用してはいけないのか」も扱います。また、本記事はシステムエンジニアリングの技術記事であり、投資助言ではありません。

まず単位の感覚 — ns、us、ms

低レイテンシチューニングを論じるには、まず時間単位の感覚が必要です。

単位大きさ感覚的な比喩該当する処理の例
1 ns10億分の1秒CPU数サイクル(1サイクル約0.3ns)L1キャッシュアクセス(約1ns)
10 ns---L2キャッシュアクセス分岐予測ミスのペナルティ
100 ns---メインメモリアクセス(約60~100ns)NUMAリモートノードアクセス
1 us100万分の1秒よく調整されたユーザー空間ネットワーキング往復の半分カーネルバイパスNIC送信
10 us---コンテキストスイッチ1回 + キャッシュ汚染標準カーネルスタックのUDP受信
100 us---設定不備なシステムの割り込み遅延スパイク深いC-stateからの起床
1 ms1000分の1秒一般Webサービスの「速い」応答SSDアクセス、GCマイナー収集
10 ms---ソウル-釜山の光ファイバ往復より長いHDDシーク、タイムスライス満了

取引システムのtick-to-trade(相場受信から注文送信まで)は、よく作られたソフトウェアベースのシステムで数マイクロ秒、FPGAベースでは数百ナノ秒の領域で競争します。つまり、私たちが戦う単位はusであり、敵は「ごく稀に発生するms級のスパイク」です。

レイテンシの源泉を分解する — パケットはどこで時間を失うか

相場パケットがNICに到着してからアプリケーションが注文を送り出すまでの経路を分解してみます。

[取引所の相場データ]
    |
    v
(1) NIC受信: ワイヤ --> NICバッファ           物理層、数十ns
    |
(2) DMA + 割り込み/ポーリング                 数百ns ~ 数us
    |     - 割り込み経路: IRQ -> softirq -> プロトコルスタック
    |     - ポーリング/バイパス経路: ユーザー空間がリングバッファを直接読む
    v
(3) プロトコル処理 (IP/UDPデコード)           標準スタック: 1~5us / バイパス: 数百ns
    |
(4) ソケット/キュー受け渡し --> アプリ起床      ここで大きな分散が発生:
    |     - スレッドが既にコアでbusy-wait中なら ~0
    |     - スケジューラの起床が必要なら 1~10us + スパイク
    v
(5) 戦略ロジック (気配計算、リスクチェック)      数百ns ~ 数us (アプリ設計の領域)
    |
(6) 注文エンコード + 送信                     (2)~(3)の逆方向
    v
[取引所の注文ゲートウェイ]

重要な洞察は2つです。第一に、平均は(3)・(5)・(6)が支配しますが、テール(裾)は(2)と(4)が支配します。割り込み集約、スケジューラの介入、C-state起床、TLBミス、SMI(システム管理割り込み)のようなイベントが99.99パーセンタイルを台無しにします。第二に、チューニングの本質は平均を減らすことではなく分散を殺すことです。トレーディングにおいて予測不可能なシステムは、遅いシステムより悪いのです。

カーネルバイパスのスペクトラム — どこまで行くか

カーネルネットワークスタックをどこまで迂回するかは、コストと効果のスペクトラムです。

アプローチ代表技術受信遅延(目安)開発難易度運用難易度適する対象
標準スタックチューニングsysctl、busy_poll、IRQアフィニティ5~20 us一般低遅延、バックオフィスフィード
XDP/AF_XDPeBPFベースのカーネル内処理2~5 usフィルタリング、相場ファンアウト、DDoS防御
ソケット互換バイパスOnload系(ソケットAPI維持)1~3 us低(再コンパイル不要)既存ソケットアプリの高速化
フルユーザー空間スタックDPDK + 独自UDP/TCP処理1 us未満tick-to-tradeのコア経路
ハードウェアオフロードFPGA/SmartNIC数百ns非常に高非常に高最上位HFT

判断基準はシンプルです。**競争がus単位か、ms単位か。**相場データ分析やリスクバッチなら標準スタックチューニングで十分であり、マッチングエンジンとの速度競争ならDPDKまたはソケット互換バイパス(Onload、VMA系)が出発点です。AF_XDPはその中間で「カーネルと協調するバイパス」という独特の位置を占め、1枚のNICで一部のトラフィックだけを低遅延経路に抜き出す構成に有用です。

CPU分離のレシピ — コアを貸し切る

低レイテンシの第一原則は「ホットパスのスレッドが走るコアから他のすべてを追い出すこと」です。カーネルブートパラメータの3点セットが基本です。

# /etc/default/grub
# 24コアマシンでコア4~23をトレーディング専用に分離する例
GRUB_CMDLINE_LINUX="isolcpus=nohz,domain,managed_irq,4-23 \
  nohz_full=4-23 \
  rcu_nocbs=4-23 \
  rcu_nocb_poll \
  irqaffinity=0-3 \
  intel_idle.max_cstate=0 processor.max_cstate=1 \
  intel_pstate=disable idle=poll \
  mitigations=off \
  transparent_hugepage=never \
  audit=0 nosoftlockup"

# 適用
sudo update-grub && sudo reboot

各項目の意味は次のとおりです。

  • isolcpus: 指定コアをスケジューラの一般的なロードバランシングから除外します。明示的にアフィニティを指定したスレッドだけがそのコアに載ります。
  • nohz_full: 該当コアで実行中のタスクが1つだけなら、周期的なスケジューラティック(既定で毎秒100~1000回)を止め、ティック割り込みによるマイクロジッタを除去します。
  • rcu_nocbs: RCUコールバック処理を分離コアからハウスキーピングコア(ここでは0~3)へ移します。
  • idle=poll / max_cstate: コアを眠らせません(後述)。
  • mitigations=off: サイドチャネル緩和機能を無効化します。システムコール・コンテキストスイッチのコストが有意に減りますが、セキュリティのトレードオフを組織が正式承認した閉域専用網の環境でのみ検討できるオプションです。

ブート後にスレッドと割り込みを配置します。

#!/usr/bin/env bash
set -euo pipefail

# 1) irqbalanceは分離環境の敵 — 無効化
systemctl stop irqbalance || true
systemctl disable irqbalance || true

# 2) すべてのIRQの既定アフィニティをハウスキーピングコア(0-3)へ
for irq in /proc/irq/*/smp_affinity_list; do
  echo "0-3" > "$irq" 2>/dev/null || true
done

# 3) トレーディングNICの受信キューIRQだけを専用コア(4,5)にピン
NIC="ens1f0"
i=0
for irq in $(grep "$NIC" /proc/interrupts | awk -F: '{print $1}'); do
  core=$((4 + i % 2))
  echo "$core" > "/proc/irq/$irq/smp_affinity_list"
  i=$((i + 1))
done

# 4) ワーカースレッドを分離コアにピン (アプリ内部でpthread_setaffinity_npが
#    正攻法だが、外部からならtaskset)
taskset -c 6 ./market_data_handler &
taskset -c 7 ./order_gateway &

# 5) カーネルワークキューもハウスキーピングコアへ
echo 0f > /sys/devices/virtual/workqueue/cpumask

検証はperfと/procインターフェースで行います。

# 分離コアでティックが本当に止まったか確認
watch -n1 'grep -E "LOC|RES" /proc/interrupts | awk "{print \$1, \$8, \$9, \$10}"'

# 特定コアのコンテキストスイッチ監視 (0であるべき)
perf stat -C 6 -e context-switches,cpu-migrations -- sleep 10

NUMAアライメント — メモリとNICのローカリティ

マルチソケットサーバーでNUMAを無視すると、メモリアクセスのたびに数十nsの税金を払います。さらに重要なのはNICがどのNUMAノードにぶら下がっているかです。PCIeスロットは特定のソケットに直結されるため、NICと同じノードのコア・メモリを使うのが原則です。

# NICのNUMAノード確認
cat /sys/class/net/ens1f0/device/numa_node
# 出力: 1  --> このNICはノード1に接続

# ノード1のコア一覧を確認
lscpu | grep "NUMA node1"

# ホットパスのプロセスをノード1のコアとメモリに束ねて起動
numactl --cpunodebind=1 --membind=1 ./trading_engine

# ノード間トラフィックがないことを確認
numastat -p $(pgrep trading_engine)

アプリケーション設計のレベルでは、相場受信スレッド → 戦略スレッド → 注文送信スレッドのチェーンをすべて同じノードに置き、スレッド間通信は同じL3キャッシュを共有するコアペアのロックフリーリングバッファで処理するのが定石です。

割り込み vs ポーリング — 起こされるのを待たない

割り込みは「イベントが来たら起こしてくれる」効率的なモデルですが、起こすコスト(IRQ処理 + スケジューラ起床 + キャッシュウォーミング)はus単位です。低遅延経路ではコアを1つ燃やしてでもポーリングします。

標準スタックを維持しつつ折衷する仕組みがbusy pollingです。

# ソケット層のbusy poll (マイクロ秒単位)
sysctl -w net.core.busy_poll=50
sysctl -w net.core.busy_read=50

# epollベースのアプリならNAPI単位のbusy poll設定も可能 (カーネル5.11以降)
# SO_BUSY_POLL_BUDGET、EPIOCSPARAMS ioctlなどアプリレベル設定と併用

# NAPIポーリングと割り込み集約のバランス — 低遅延方向は集約オフ
ethtool -C ens1f0 adaptive-rx off adaptive-tx off rx-usecs 0 tx-usecs 0

割り込み集約(coalescing)をオフにすると、PPSが高いときにCPU使用量が跳ね上がる点に注意します。相場のように「1パケットごとが即座に重要」なトラフィックでのみオフにし、バルクトラフィックのNICは集約を維持します。DPDKやOnloadを使う場合、この折衷自体が消え、ユーザー空間ポーリングモードが既定になります。

アイドル状態の排除 — 眠ったコアは遅い

現代のCPUはアイドル時にC-stateで眠り、負荷に応じてP-state(周波数)を変えます。どちらも低レイテンシの敵です。深いC-state(C6)からの起床には数十~数百usかかることがあり、周波数遷移も数十usのヒカップを生みます。

# C-state: ブートパラメータで固定済みなら確認のみ
cat /sys/devices/system/cpu/cpu6/cpuidle/state*/disable

# ランタイムで制御するなら/dev/cpu_dma_latencyに0をwriteしたまま維持
# (ファイルを開いたプロセスが生きている間適用 — tunedのlatency-performanceがこの方式)
tuned-adm profile latency-performance

# P-state: governorをperformanceに固定
for g in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
  echo performance > "$g"
done

# ターボブースト: 決定論を求めるならオフが一貫的
# (オンにすると平均は速くなるが、熱条件で周波数が揺れる)
echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo

見落としやすい点が2つあります。第一に、**SMI(System Management Interrupt)**はOSが認知できないファームウェアレベルの停止で、数百usのスパイクの常連犯です。BIOSでUSBレガシーサポートや電源管理関連のSMIソースを無効化し、turbostatのSMIカウンタで監視します。第二に、ハイパースレッディングはホットパスのコアでは無効化するか、兄弟スレッドを空けておきます。兄弟がキャッシュと実行ポートを汚染するためです。

メモリ — ページフォルトとTLBミスを撲滅する

ホットパスでのページフォルト1回はus単位の災害です。起動時点ですべてのメモリに触れてロックします。

# ヒュージページ(2MB)の予約 — TLBミス削減
echo 4096 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# 1GBヒュージページはブートパラメータで
# default_hugepagesz=1G hugepagesz=1G hugepages=16
/* アプリケーション起動シーケンスの定石 */
#include <sys/mman.h>

int main(void) {
    /* 1) 現在と将来のすべてのページをRAMに固定 */
    mlockall(MCL_CURRENT | MCL_FUTURE);

    /* 2) ヒープ/プールを事前確保し全ページにタッチ (prefault) */
    /* 3) ホットパス進入後はmalloc/free、システムコール、ページフォルト禁止 */
    return run_engine();
}

THP(Transparent Huge Pages)は議論の的です。TLBの観点では利益ですが、khugepagedがバックグラウンドでページを結合する瞬間にms級の停止が来る可能性があります。低レイテンシ陣営の通説はTHPはneverで無効化し、明示的なヒュージページを使うです。決定論が目的なら「カーネルが勝手にやってくれる」機能は大半をオフにする方向が正しいのです。

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
sysctl -w vm.swappiness=0
sysctl -w vm.stat_interval=120        # vmstat更新周期も延ばしてジッタ削減

ネットワークスタックのチューニングパラメータ

標準スタックを使う経路(注文ゲートウェイのTCPなど)の基本セットです。

# バッファ: 低遅延の観点では「大きく」ではなく「適切に」 — 過大バッファは遅延を隠蔽
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216

# TCP: Nagleはアプリ側でTCP_NODELAYで切るのが正攻法
sysctl -w net.ipv4.tcp_low_latency=1        # 古いカーネルでのみ意味あり
sysctl -w net.ipv4.tcp_timestamps=1
sysctl -w net.ipv4.tcp_slow_start_after_idle=0

# キュー規律: 送信遅延の最小化
sysctl -w net.core.default_qdisc=fq
tc qdisc replace dev ens1f0 root mq

# UDP相場受信: ドロップ監視が生命線
watch -n1 'cat /proc/net/udp | awk "{print \$13}" | sort | uniq -c'
ethtool -S ens1f0 | grep -iE "drop|miss|fifo"

# RSS/RPS: 相場マルチキャストグループごとに受信キューを分離しコアへ1:1マッピング
ethtool -X ens1f0 weight 1 1 0 0

時刻同期 — PTPとハードウェアタイムスタンプ

レイテンシを減らすことと同じくらい重要なのがレイテンシを正確に測ることであり、その前提は時計です。規制面でも、欧州MiFID IIのRTS 25はHFT事業者にUTC比100マイクロ秒以内の時計精度と精密なタイムスタンプ記録を要求しています。NTP(ms級)では不足で、PTP(IEEE 1588、us未満)が標準です。

# NICのハードウェアタイムスタンプ対応確認
ethtool -T ens1f0
# "hardware-transmit / hardware-receive / PTP Hardware Clock: 0" を確認

# linuxptp: NICのPHCをグランドマスターに同期
ptp4l -i ens1f0 -f /etc/ptp4l.conf --summary_interval 6 &

# PHC --> システムクロック同期
phc2sys -s ens1f0 -O 0 -u 64 &

# オフセット監視 (rmsで数十nsレベルが目標)
pmc -u -b 0 'GET CURRENT_DATA_SET'

アプリケーション側ではSO_TIMESTAMPINGソケットオプションでNICハードウェア受信タイムスタンプを受け取り、「ワイヤ到着時刻」基準の区間別レイテンシを計測します。ソフトウェアタイムスタンプは測定対象(カーネル経路)が測定道具に含まれる矛盾があるため、本格的な測定はハードウェアタイムスタンプ、または光タップ + キャプチャ装置で行います。

測定方法論 — 平均は嘘をつく

低レイテンシシステムの成績表は平均ではなくパーセンタイル、特に99.9、99.99パーセンタイルです。

あるシステムのtick-to-trade分布 (架空の例)
  p50    :   3.2 us     <-- マーケティング資料に載る数字
  p99    :   5.8 us
  p99.9  :  14.0 us     <-- 割り込み/タイマーの介入
  p99.99 : 180.0 us     <-- C-state起床、SMI、ページフォルト
  max    : 2.1 ms       <-- これが最悪のタイミングで来れば事故

ヒストグラムなしで平均(3.5us)だけ見れば、このシステムは「素晴らしい」。
しかし1万回に1回、市場が最も荒れているとき(=負荷が最も高いとき)に
180usが発生するなら、まさにその瞬間こそお金を失う瞬間です。

ツールチェーンは次のとおりです。

# 1) プラットフォーム自体のジッタ測定 — cyclictest (rt-testsパッケージ)
#    分離コアで20分、ヒストグラム出力
cyclictest -m -p95 -t1 -a 6 -i 100 -h 400 -D 20m -q

# 2) カーネル経路のトレース — bpftraceでsoftirq処理時間の分布
bpftrace -e 'tracepoint:irq:softirq_entry { @ts[cpu] = nsecs; }
tracepoint:irq:softirq_exit /@ts[cpu]/ {
  @dist = hist(nsecs - @ts[cpu]); delete(@ts[cpu]); }'

# 3) スケジューラ介入の監視 — 分離コアで起きてはいけないイベント
perf record -C 6 -e sched:sched_switch,sched:sched_wakeup -- sleep 60
perf script | head

# 4) ハードウェアカウンタでキャッシュ/TLBミスの相関分析
perf stat -C 6 -e cycles,instructions,cache-misses,dTLB-load-misses -- sleep 10

アプリケーションレベルではHdrHistogram系の無損失ヒストグラムで全区間を常時記録し、「coordinated omission(協調的欠落)」の罠 — 測定ループ自体が止まっていた間のサンプルが欠けて分布が良く見える現象 — を補正することが重要です。測定は必ず本番同等のメッセージレートで、市場オープン直後のようなバースト条件を再現して行います。

アプリケーション側の考慮 — カーネルだけで終わりではない

カーネルが提供するのは「邪魔のないコア」までです。その上のコードも同じ規律を守る必要があります。

  • Java系: ホットパスでゼロアロケーション設計にし、GC自体を回避します。オブジェクトプール、プリミティブベースのデータ構造、オフヒープリングバッファ(Aeron、Chronicle系のパターン)が標準で、GCは場が引けた後のみ許可する運用も一般的です。ZGCのような低停止コレクタもms未満の停止を保証するだけで、ゼロではありません。
  • C++系: ホットパスでの動的確保、ロック、システムコール、例外を禁止します。スピンで待ち、分岐を減らし、ホットデータをキャッシュライン単位で整列し、false sharingをパディングで除去します。
  • 共通: ログは非同期 + バイナリ + 別コアで。ホットパスのprintf一行が、すべてのカーネルチューニングを無力化し得ます。

なぜ一般サービスには過剰なのか — トレードオフの精算

本記事のレシピを一般サービスに適用すべきでない理由を精算してみます。

技法トレーディングでの利益一般サービスでのコスト
コア分離 + ポーリングテール遅延のus単位短縮コア常時100%占有 — 電力・コスト浪費、集積度低下
C-state無効化起床遅延の除去アイドル電力が数倍、発熱、ターボ余力減少
mitigations=offシステムコール数百ns短縮サイドチャネル脆弱性の露出 — マルチテナントでは禁忌
割り込み集約オフパケットごとの即時処理高PPSでCPU急増、スループット急落
THPオフ + 手動ヒュージページms級停止の除去運用複雑度の増加、一般ワークロードではTHPが有利な場合も多い
カーネルバイパスus未満の受信カーネルのセキュリティ・可観測性ツールが無力化、専任要員が必要

要約すると、低レイテンシチューニングはスループット・電力効率・セキュリティ・運用性を支払って決定論を買う取引です。99パーセンタイルで50msあれば十分なAPIサーバーなら、この取引は明白な損です。逆にusが損益となるシステムなら、上記のコストはすべて正当化されます。自分のシステムがどちら側なのかを正直に判断することが、最も重要なチューニングです。

チェックリスト

ハードウェア/ファームウェア

  • BIOSでC-state制限、ハイパースレッディングポリシー、SMIソースの点検を完了したか
  • NICがPTPハードウェアタイムスタンプに対応し、PCIeスロットが正しいNUMAノードにあるか
  • turbostatでSMIカウンタがゼロ近くに維持されることを確認したか

カーネルブート/分離

  • isolcpus、nohz_full、rcu_nocbsが同一のコア集合で設定されているか
  • irqbalanceが無効化され、IRQアフィニティが明示的に配置されているか
  • 分離コアでperfによりコンテキストスイッチゼロを確認したか

メモリ/電力

  • mlockallとプリフォルトが起動シーケンスに含まれているか
  • THPがneverで、明示的ヒュージページが予約されているか
  • governor performance、C-state固定、ターボポリシーが意図どおりか

ネットワーク/時刻

  • 相場NICの割り込み集約・ドロップカウンタ・RSSマッピングを点検したか
  • ptp4l/phc2sysのオフセットが目標(数十ns)内に維持されているか
  • ハードウェアタイムスタンプベースの区間別計測が動作しているか

測定/運用

  • p99.99とmaxを常時ヒストグラムで記録しているか
  • cyclictestベースのプラットフォームジッタ基準線が文書化されているか
  • チューニング変更ごとに前後の分布を比較する手順があるか (一度に1つだけ変更)

落とし穴とアンチパターン

  • 一度に10個変える。 どの変更が効いたのか分からなくなります。基準線測定 → 単一変更 → 再測定のループを守ります。
  • 平均だけ見てリリース。 テールは負荷が高いとき、つまり市場が荒れているときに現れます。閑散時間のベンチマークはほぼ無意味です。
  • 分離したのにカーネルスレッドが残っている。 kworker、ksoftirqd、タイマーが分離コアに残ることがあります。ワークキューのcpumaskとnohz_fullの動作を実測で確認すべきです。
  • NUMAを忘れたアップグレード。 サーバー交換やNICスロット変更後にnuma_nodeが変わり、静かに遅くなる事故はよくあります。起動時にアサーションとして埋め込むことを勧めます。
  • mitigations=offの無神経なコピー。 インターネットに露出する、あるいはマルチテナントのシステムでこのオプションをコピーして使うのは、セキュリティ事故への近道です。
  • 時計なしの測定。 同期されていない2台のホストのタイムスタンプ差で「レイテンシ」を計算すると、負のレイテンシのような喜劇が起きます。PTPが先です。

おわりに

低レイテンシカーネルチューニングの技術的内容は、結局一文に要約されます。**「ホットパスから予測不可能なすべてを取り除け。」**スケジューラの善意も、電源管理の節約も、カーネルの便利機能も、ホットパスの上ではすべてジッタの源泉です。だからこの作業は機能を足すチューニングではなく機能を引くチューニングであり、引いた跡を測定で埋める作業なのです。

マイクロ秒との戦争に勝つチームの共通点は、華麗な秘法ではなく、パーセンタイルヒストグラムを毎日見る習慣と、変更の一つひとつを計測で検証する規律でした。測定なきチューニングは迷信であり、測定あるチューニングは工学です。

参考資料