C++、Rust、および高頻度取引: 決定論的なレイテンシーが議論を決定する場合

C++、Rust、および高頻度取引: 決定論的なレイテンシーが議論を決定する場合

C++、Rust、および高頻度取引: 決定論的なレイテンシーが議論を決定する場合

導入

ほとんどのシステムには多少の余裕があるため、プログラミング言語に関する議論は通常容認されます。サービスは少し非効率で、キューは必要以上に広くなり、再試行ポリシーは道徳的に疑わしいことを行い、製品はまだ動作し、収益はまだ得られているため、誰もが動き続けますが、レイテンシーチャートは存続可能な形で醜いものです。

高頻度取引はそれほど感情的ではありません。この四半期にどの言語がインターネットで優勝したかは関係ありません。市場データが状態になるか、状態が決定になるか、ウィンドウが閉じる前にその決定が注文になるかが重要です。そのような環境では、測定に耐えられないエレガントな意見はすぐに、通常は警告なしに強盗されます。

だからこそ、HFT の C++ と Rust の問題は興味深いのです。それは、一方の言語が神聖で、もう一方の言語が詐欺的だからではなく、HFT が実際のシステム動作で議論全体を無駄にする稀なドメインの 1 つであるためです。ホット パスは、圧力を受けてもその形状を維持するか、維持しません。テール レイテンシーは規律が保たれるか、規律が保たれないかのどちらかです。リプレイは真実を伝えるか、そうでないかのどちらかです。そこでは建築は性格テストではありません。請求書です。

これが、答えが「C++ 永遠に」または「Rust ですべてを書き換える」ではない理由でもあります。なぜなら、安全性は良く、恐怖はビジネス モデルだからです。より正直な答えは範囲が狭いため、より有用です。 C++ は依然として最もホットな HFT パスを支配しています。これは、ツール、フィード処理、メモリ制御、プロファイリング、およびハードウェアに隣接した実践の周囲の世界が依然として極端に C++ の形をしているためです。 Rust はそのコアの周りで、そして時にはそのコアの慎重に選択された部分の内部で本当に役立ちますが、低レイテンシーのトレーディングは、ほとんどのチームがイニシアチブの名前を変更するよりも早く抽象化の間違いを罰するという基本的な事実を消去するものではありません。

したがって、正しい会話はアイデンティティについてではありません。それはシステムの境界に関するものです。スタックのどの部分が、メモリ、レイアウト、キュー、アフィニティ、およびワイヤの動作を大幅に制御する必要があるでしょうか?より強力な正当性制約とより安全なデフォルトから最も恩恵を受けるのはどの部分でしょうか?どの部分が部族の純度ではなくハイブリッドな扱いに値するのでしょうか?これらの質問は、言語の説教に比べてはるかに魅力的ではありませんが、生産との接触を経て生き残る質問でもあります。

HFT によって悪い技術哲学が高価に見える理由

HFT は、エンジニアリングに関するよく知られた嘘、つまり平均的な行動で十分であるという嘘を暴くのが異常に上手です。多くの一般的な製品では、スループット、再試行、またはユーザーの忍耐の背後に時折発生する混乱を隠しながら、システムは立派な状態を維持できます。 HFT では、平均レイテンシは興味深いものですが、テール動作は実際に屈辱を与える部分であることがよくあります。間違ったタイミングで動作するまでは高速に見えるシステムは、商業的に意味のある意味では高速システムではありません。ベンチマークも付いている自信作です。

HFT エンジニアが不正確な抽象化にアレルギーを持つのはこのためです。彼らは、ホット パス上の 1 つの追加の割り当てが「単なる 1 つの割り当て」ではないことを学びます。これはジッターの原因となる可能性があります。 1 つのキュー ホップは、「単なる 1 つのキュー ホップ」ではありません。ここも時間が蓄積され、調整が拡張され、視界が悪化する場所です。キャッシュに敵対的な構造の 1 つは、単なる美的欠陥ではありません。これは、システムを通過するあらゆる市場イベントに対して継続的に課される税金です。これに実際のフィード量を掛け合わせると、突然、スライドデッキからのデザインの選択が予算内で繰り返し表示される項目になり、失望することになります。

Rust は、メモリの安全性が重要であり、同時実行性の正確性が重要であり、システム コードは「穴の上でナイフをジャグリングしている間は注意してください」よりも適切なデフォルトに値するため、正当な力を持ってこの会話に参加します。その部分は本当です。しかし、HFT は真実を単独で評価するものではありません。それは真実を組み合わせたものに報酬を与えます。安全性は重要です、はい。成熟したフィード ハンドラー、安定した ABI 境界、リプレイ ツール、プロファイル主導のイテレーション、成熟した取引所統合文化、市場が不親切なときにマシンが何をしているかを正確に検査する機能も同様です。 C++ は、ほとんどの HFT 環境で、より多くの周囲のインフラストラクチャとともに提供されます。

これが、バイヤーとエンジニアリングのリーダーが純粋な物語に抵抗すべき理由の 1 つです。言語は、狭い次元では優れていても、周囲のエコシステム、ツール、チームのエクスペリエンスが実際の配信パスをサポートしていない場合、スタックの最もタイミングに敏感な部分にとっては間違ったデフォルトである可能性があります。 HFT は、地元の素敵な真実が、道全体が依然として重要であることを学ぶために行く場所です。

スタックは 1 つのものではないため、言語の選択は別のふりをすべきではありません

深刻なシステム作業における最も愚かな間違いの 1 つは、「HFT スタック」について、あたかも 1 つの優先言語を備えた単一の技術的有機体であるかのように語ることです。そうではない。これは、非常に異なる圧力と障害コストを持つパスの集合です。

市場データの取り込みパスには 1 つの性質があります。オーダーブック更新パスには別のパスがあります。戦略ロジックは数値的には緻密ですが、構造的には狭い場合があります。リスクチェックは多くの場合、遅延に敏感ですが、退屈で大人向けの、法的に重要な方法で正確性にも敏感です。シミュレーションとリプレイのインフラストラクチャでは、生のナノ秒の虚栄心よりも決定論と内省が重視される場合があります。コントロール プレーン ツール、展開ヘルパー、およびオペレーター サーフェスは、顧客が決して目にすることのないパスから 5 マイクロ秒を削減することよりも、信頼性、保守性、統合の健全性をはるかに重視しています。

これは、賢明な C++ と Rust の会話が始まる場所であることが多いため、重要です。 C++ は、そのパスが非常にホットで、ハードウェアを重視し、統合が重視され、すでに長年のネイティブ運用慣行に囲まれている場合に最も強力なままです。 Rust は、パスが依然として重要であるにもかかわらず、より強力なデフォルト、より明確な所有権、より狭いメモリリスクエクスポージャの経済的価値がエコシステムの摩擦のコストを上回る場合に、より魅力的になります。

実際には、それはハイブリッドな結果につながることがよくあります。最もホットなフィード処理とゲートウェイ パスは C++ に残ります。再生ツール、構成検証、特定のリスクサイド ヘルパー、メッセージ正規化ユーティリティ、監査ツール、またはオペレータ向けの内部コンポーネントが、優れた Rust 候補となる可能性があります。これは優柔不断ではありません。それは建築的な大人です。このシステムは、データセンターを備えた言語ファンダムとしてではなく、実際の境界のセットとして扱われています。

C++ が依然として最もホットなパスを所有している場所

C++ が HFT の中での地位を保っているのは、部外者が想像するほど神秘的ではない理由によるものです。 1 つ目の理由は、メモリとレイアウトの制御です。 HFT ホット パスは、どのデータが一緒に存在するか、構造がキャッシュ内でどのように動作するか、負荷がかかっているときに所有権がどのように表示されるか、市場が礼儀正しくなくなったときにシステムが割り当て規律を維持できるかどうかを考慮します。 C++ は依然として、エンジニアにこれらの選択に対して異常に直接的な影響力を与えており、どの「小さな」コストが密かに大きなコストであるかを学習するためにすでに数十年を費やしてきたエコシステム内でそれを行っています。

2 番目の理由は、ツールの密度です。 HFT の C++ は言語だけを意味するものではありません。それは、コンパイラー、サニタイザー、フレーム グラフ、C++、VTune、リプレイ ハーネス、交換アダプター、キューイングの伝承、アロケーターの専門知識、そして財政的圧力の下で蓄積された膨大な量のパフォーマンス戦争の物語を意味します。そこではチームはゼロからスタートするわけではありません。彼らは深い運用文化を継承しており、HFT は修辞上の清潔さよりも測定された反復にはるかに多くの報酬を与えるため、その文化は重要です。

3 番目の理由は、統合重力です。交換、ネイティブ ネットワーク パス、パケット キャプチャ ツール、カーネル隣接の最適化、FPGA 隣接のインフラストラクチャ、および低遅延エコシステム全体は、C および C++ の世界でも依然として非常に快適です。 Rust はその世界と対話することができ、場合によっては非常に効率的に対話できますが、「対話できる」ということと、「システム全体で摩擦が最小の経路である」ということは同じではありません。深刻なHFTでは、摩擦は感情的な不都合ではありません。これは、レイテンシー税、デバッグ税、配送税が同時にかかる可能性があります。

AI の時代ではより重要な、微妙な理由もあります。C++ は、単にこの作業に使用できる操作メモリが増えただけです。 AI コーディング システム、コード検索、公開サンプル、ベンダー スニペット、オプティマイザーの伝承、デバッグ トレイルは、Rust 付近よりも低遅延システムの C++ 付近に密集しています。それは C++ をより高貴にするものではありません。これにより、人間と AI ツールが、何年も前に魅力が切れた醜い実際のコードベース内で共同作業することが容易になります。

Rust が道徳を実行する代わりに実際に役立つ場所

Rust は、アーキテクチャ図の個性的なアクセサリーとして機能するのではなく、実際の問題を解決するときに最も役立ちます。 HFT では、最も強力な Rust のユースケースは、ホット コアの絶対的な中心ではなく、ホット コアの周囲に現れることがよくあります。

Rust は、正確性の障害が高くつくが、レイテンシ バジェットが顕微鏡で測定できないコンポーネントに役立ちます。メッセージ検証層、構成および展開ツール、特定のプロトコル正規化パス、制御サービス、管理ユーティリティ、オフライン アナライザー、および内部オペレーター ツールは、言語の明示性への偏りから恩恵を受けることができます。ポイントは、モダンに見えないことです。重要なのは、より重要な作業から注意を奪う、愚かで反復的な、構造的に回避可能なミスの種類を減らすことです。

Rust は、チームが適切な専門知識を持ち、境界が正直であれば、慎重に選択されたニアホットコンポーネントにも役立ちます。チームが FFI と割り当てのストーリーを制御でき、周囲のエコシステムへの負荷が誰も望んでいないロールアウト中の午前 2 時 40 分に発見されるのではなく、事前に理解されている場合には、低レイテンシのパーサー、境界付きステート マシン、または決定論的インフラストラクチャが Rust の有力な候補となる可能性があります。

しかし、これはまさにチームに規律が必要な場面です。 Rust は、信仰に基づく改修としてネイティブ取引スタックの真ん中にドロップされた場合、価値がありません。境界が明確で、測定パスが明白で、統合による運用コストが安全性や保守性の向上よりも低い場合に価値があります。そうでなければ、このプロジェクトは、不確実性を横に移動させるためにエンジニアリングに真剣な時間を費やす方法についての美しいケーススタディになります。

説教よりも境界が重要

C++ と Rust の議論でよくある間違いは、Rust を使用すると危険が自動的に除去されると想定していることです。そうではありません。危険がどこにあるかによって変わります。 HFT では、ホット パスが言語行で終了することはほとんどないため、境界の問題は特に重要です。それらは、ネットワーク境界、キュー境界、スケジューリング境界、FFI 境界、およびデータ レイアウト境界で終わります。

Rust コンポーネントが C++ 交換アダプターに交差したり、ネイティブ キューと通信したり、レイアウトを厳密に仮定して戦略エンジンにデータを渡したり、境界遷移を越えて決定論的な動作を維持したりする必要がある場合、実際のエンジニアリング作業は「Rust を使用した」というものではありません。実際の作業は、縫い目がどれだけ慎重に定義され、検証されたかによって決まります。 ABI の不一致、所有権の混乱、隠しコピー、キューの間違い、またはタイミングの予期せぬ事態によって、安全でない動作が発生する可能性があります。言語だけがガバナンス モデルとなるわけではありません。境界線は。

これが、成熟したチームが狭いホットパスと狭くて危険な路面について話す理由です。彼らは、システム設計の根本的な問題を解決するために、「デフォルトでメモリの安全性」のようなスローガンに依存しません。優れたチームは、より醜い、したがってより有益な質問をします。コピーはどこで行われますか?キューホップはどこにありますか?どちら側がバッファを所有しているのでしょうか?どのパスが割り当てられますか?背圧がかかると何が起こるのでしょうか?リプレイ可能とは何ですか?ローカルな勝利が世界的な失望となるという長い伝統があるため、何を単独でベンチマークできるのか、また何をエンドツーエンドでベンチマークしなければならないのか?

最初に解決する価値のある実際的なケース

最も賢明な最初のプロジェクトが「ホット パスを書き換える」ことはめったにありません。これは、家に入って、どのパイプがすでにキッチンに水浸しになっているかを確認する前に、最初の有益な行為は骨組み全体を交換することであると判断するのと技術的に同等です。

最初のプロジェクトとしては、次のいずれかが適しています。

フィードハンドラーの証拠作業

解析、正規化、キューイング、ハンドオフが本当に遅延の問題なのかどうかについてチームが議論する場合は、まず証拠のパスを構築します。代表的なトラフィックをキャプチャし、それを決定論的に再生し、時間とジッターが実際にチェーンに入っている場所をシステムに告白させます。ほとんどの HFT システムでは、これ以上のイデオロギーは必要ありません。彼らにはより優れた嘘発見器が必要です。

ゲートウェイとリスク境界のクリーンアップ

多くのスタックは、コア戦略ロジックによって台無しになることはありません。これらは、リスク、ゲートウェイ ロジック、運用調整の間の境界がずさんであるために台無しになります。これらの継ぎ目で慎重に書き直したり再構築したりすることで、絶対的に最もホットなループに最初に触れるという商業的リスクを伴うことなく、信頼性と診断可能性を向上させることができます。

ハイブリッド コントロール プレーンのクリーンアップ

オペレータ ツール、展開ヘルパー、回復ユーティリティ、または再生ツールが脆弱な場合、Rust が有力な候補となる可能性があります。これらのコンポーネントは、最速のマイクロ秒パスに属さない場合でも、組織全体の健全性を形作ることがよくあります。クリーナー ツールは、資産内のすべてのバイナリが同じ言語に値するかのように装うことなく、ホットなシステムを穏やかにすることができます。

ハンズオン ラボ: 小さな配列ギャップ検出器を構築し、それを正直にする

研究室を小さくて有用なものに保ちましょう。 HFT システムは、魅力的な戦略ロジックに到達するずっと前に、シーケンス規律によって生きて死んでいきます。このおもちゃのプログラムはフィードのようなストリームを再生し、ギャップが発生した場所を報告します。

main.cpp

#include <cstdint>
#include <iostream>
#include <string>
#include <vector>

struct Packet {
    std::uint64_t seq;
    std::string payload;
};

struct Gap {
    std::uint64_t expected;
    std::uint64_t received;
};

class GapDetector {
public:
    void on_packet(const Packet& packet) {
        if (!started_) {
            expected_ = packet.seq + 1;
            started_ = true;
            return;
        }

        if (packet.seq != expected_) {
            gaps_.push_back({expected_, packet.seq});
        }

        expected_ = packet.seq + 1;
    }

    const std::vector<Gap>& gaps() const {
        return gaps_;
    }

private:
    bool started_ = false;
    std::uint64_t expected_ = 0;
    std::vector<Gap> gaps_;
};

int main() {
    std::vector<Packet> replay{
        {1001, "AAPL bid"},
        {1002, "AAPL ask"},
        {1003, "MSFT bid"},
        {1007, "MSFT ask"},
        {1008, "NVDA bid"},
        {1011, "NVDA ask"}
    };

    GapDetector detector;
    for (const auto& packet : replay) {
        detector.on_packet(packet);
    }

    if (detector.gaps().empty()) {
        std::cout << "no gaps\n";
        return 0;
    }

    for (const auto& gap : detector.gaps()) {
        std::cout << "gap expected=" << gap.expected
                  << " received=" << gap.received << "\n";
    }
}

建てる

Linux または macOS の場合:

g++ -O2 -std=c++20 -o gap_detector main.cpp
./gap_detector

Windows の場合:

cl /O2 /std:c++20 main.cpp
.\main.exe

この小さな練習がなぜ重要なのか

なぜなら、それは正しい考え方を強制するからです。

  • 確定的な状態更新
  • 正直な順序付け
  • 理論の前にリプレイ
  • 制限された測定可能な動作

これはすでに、驚くべき数の会議スライドよりも HFT の数になります。

愛好家向けのテストタスク

  1. 同じディテクタを Rust に移植し、ベンチマークの虚偽性ではなく、境界の明確さ、依存関係の摩擦、各バージョンが既存のツールにどれだけ簡単に適合するかを比較します。
  2. 欠落したパケットが後で順序どおりに到着しないようにリプレイを延長し、検出器がパケットをバッファリングするか、拒否するか、フラグを立てるかを決定します。
  3. タイミングを追加し、ベクトル バック リプレイとリング バッファ バック リプレイの違いを測定します。
  4. ホット パスに不必要な割り当てを 1 つ導入し、「小さな」決定がどれだけ早く結果を汚染し始めるかを測定します。
  5. on_packet 内にロギング ブランチを追加し、不注意に配置した場合に可観測性がどれほど早く妨害されるかを観察します。

まとめ

HFT における実際の C++ と Rust の会話は、どの言語がより優れた神話に値するかということではありません。システムのどの部分を直接制御する必要があるか、どの部分がより強力なデフォルトから恩恵を受けるか、どの境界を妄想することなくハイブリッド設計をサポートできるほど正直にできるかが重要です。

C++ は依然として最もホットな HFT パスを支配しています。これは、このドメインがメモリ レイアウト、キューイング、ワイヤ動作、プロファイリング、リプレイ、成熟した低レイテンシー エコシステムとの統合を制御できるためです。 Rust は、正確性、明示性、保守性が追加のエコシステム摩擦コストよりも大きな価値を生み出す場合に役立ちます。どちらも深刻なスタックに属することができます。大人の行動は、どこで行うかを決定し、言語ファンではなく証拠に点数を付けさせることです。

参考文献

  1. NASDAQ TotalView-ITCH 仕様: ITCH
  2. FIX 取引コミュニティ標準: FIX
  3. DPDK ドキュメント: https://doc.dpdk.org/guides/
  4. Linux タイムスタンプに関するドキュメント: Linux
  5. Brendan Gregg が Flame Graph について語る: https://www.brendangregg.com/flamegraphs.html
  6. Rust パフォーマンス ブック: Rust
Philip P.

Philip P. – CTO

ブログに戻る

接触

会話を始める

明確な線が数本あれば十分です。システム、プレッシャー、そして妨げられた決断について説明してください。 または直接書いてください midgard@stofu.io.

01 システムが行うこと
02 今何が痛いのか
03 どのような決定がブロックされているのか
04 オプション: ログ、スペック、トレース、差分
0 / 10000
ファイルが選択されていません