1.ネットワークIOの状況と傾向
ユーザーの利用から、ネットワーク速度が向上し、ネットワーク技術の開発も1GE / 10GE / 25GE / 40GE / 100GEから進化していることがわかり、ネットワークIO機能と結論付けることができます。単一のマシンのは、時代の発展に追いつく必要があります。
ルーター、スイッチ、ファイアウォール、基地局、その他の機器など、従来の通信分野のIP層以下は、すべてハードウェアソリューションを使用しています。専用ネットワークプロセッサ(NP)に基づいており、FPGAに基づいているものもあれば、ASICに基づいているものもあります。ただし、ハードウェアベースの欠点は非常に明白です。バグは修復が容易ではなく、デバッグと保守が困難であり、ネットワークテクノロジーが開発されています。たとえば、2G / 3G / 4G / 5Gなどのモバイルテクノロジーの革新です。これらのビジネスロジックは、ハードウェアに基づいて実装するには非常に苦痛です。すばやく繰り返します。従来の分野での課題は、ソフトウェアアーキテクチャを備えた高性能ネットワークIO開発フレームワークの緊急の必要性です。
クラウドの開発プライベートクラウドの出現は、ハードウェアを共有するネットワーク機能仮想化(NFV)を通じてトレンドになっています。NFVは、標準サーバーと標準スイッチを介したさまざまな従来または新しいネットワーク機能の実現として定義されています。一般的なシステムと標準サーバーに基づく高性能ネットワークIO開発フレームワークが緊急に必要とされています。- スタンドアロンパフォーマンスの急上昇
.1Gから100Gへのネットワークカードの開発、およびシングルコアからマルチコア、マルチCPUへのCPUの開発により、サーバーのスタンドアロン機能は横行することで新たな高みに達しました拡張。しかし、ソフトウェア開発はリズムに追いつかず、1台のマシンの処理能力はハードウェアの扉に匹敵するものではありません。時代に対応し、1台のマシンには数百万台の高スループットサービスを開発する方法同時機能の。高いQPSを必要としないビジネスでも、主にCPUを集中的に使用しますが、ビッグデータ分析、人工知能、その他のアプリケーションは、ジョブを完了するために分散サーバー間で大量のデータを送信する必要があります。この点は、私たちのインターネットバックエンド開発にとって最も懸念され、最も関連性があるはずです。
2. Linux + x86ネットワークIOのボトルネック
数年前、Linuxでメッセージを送受信するプロセスを説明した記事「ネットワークカードの動作原理と高並行性の下でのチューニング」を書きました。経験によると、C1(8コア)でアプリケーションを実行するには、1Wパケット処理ごとに1%のソフト割り込みCPUを消費する必要があります。つまり、1台のマシンの上限は100万PPS(パケット/秒)です。100万PPSのTGW(Netfilterバージョン)のパフォーマンスから、AliLVSはわずか150万PPSに最適化されており、使用するサーバーの構成は依然として比較的良好です。フル10GEネットワークカードを実行する場合、64バイトの各パケットには2,000万PPSが必要です(注:最小フレームサイズが84Bであるため、イーサネット10ギガビットネットワークカードの速度の上限は1,488万PPSです。 "帯域幅、1秒あたりのパケット数、およびその他のネットワークパフォーマンスメトリック)、100Gは2億PPSです。つまり、各パケットの処理時間は50ナノ秒を超えることはできません。キャッシュミスでは、TLB、データキャッシュ、コマンドキャッシュのいずれであっても、ミスが発生します。メモリに読み戻すのに約65ナノ秒、NUMAシステムのノード間で通信するのに約40ナノ秒かかります。したがって、ビジネスロジックを追加しなくても、純粋にパケットを送受信することは非常に困難です。キャッシュのヒット率を制御する必要があり、クロスノード通信が発生しないようにコンピューターアーキテクチャを理解する必要があります。
これらのデータから、ここでの課題の大きさ、理想と現実を直接感じることができれば、バランスを取る必要があります。問題はこれらを持っています
- メッセージを送受信する従来の方法では、通信にハード割り込みを使用する必要があります。各ハード割り込みは約100マイクロ秒を消費しますが、これはコンテキストの終了によって引き起こされるキャッシュミスとしてカウントされません。
- データをカーネルモードからユーザーモードに切り替える必要があります。これにより、CPUの消費量が多くなり、グローバルロックの競合が発生します。
- パケットの送信と受信の両方に、システムコールのオーバーヘッドがあります。
- カーネルは複数のコアで動作し、グローバルに一貫性があります。ロックフリーを使用しても、ロックバスとメモリバリアによるパフォーマンスの低下は避けられません。
- ネットワークカードからビジネスプロセスへのパスが長すぎます。netfilterフレームワークなど、実際には不要なものもあります。これは、特定の消費をもたらし、キャッシュミスを起こしやすいものです。
3、DPDKの基本原則
以前の分析から、IOの実装方法、カーネルのボトルネック、およびカーネルを介したデータフローの制御不能な要因を知ることができます。これらはすべてカーネルに実装されています。カーネルがボトルネックの原因です。問題を解決するには、カーネルをバイパスする必要があります。したがって、主流のソリューションは、ネットワークカードIOをバイパスし、カーネルをバイパスし、ユーザーモードでパケットを直接送受信して、カーネルのボトルネックを解決することです。
LinuxコミュニティはバイパスメカニズムNetmapも提供しています。公式データは10Gネットワークカードで1400万PPSですが、Netmapは広く使用されていません。これにはいくつかの理由があります。
- Netmapはドライバーのサポートを必要とします。つまり、ネットワークカードの製造元はこのソリューションを承認する必要があります。
- Netmapは依然として割り込み通知メカニズムに依存していますが、これはボトルネックを完全には解決しません。
- Netmapは、いくつかのシステムコールに似ており、パケットを直接送受信するユーザーモードを実現します。関数は原始的すぎ、依存するネットワーク開発フレームワークを形成せず、コミュニティは完全ではありません。
それでは、10年以上にわたって開発されてきたDPDKを見てみましょう。Intelの主要な開発からHuawei、Cisco、AWSなどの主要メーカーの追加まで、コアプレーヤーはすべてこのサークルに含まれています。コミュニティ、そして生態系は閉ループを形成しています。初期の頃は、主に従来の通信分野の第3層より下のアプリケーションでした。たとえば、Huawei、China Telecom、China Mobileはすべて初期の採用者であり、スイッチ、ルーター、ゲートウェイが主なアプリケーションシナリオでした。ただし、上位レベルのサービスの要件とDPDKの改善により、より高度なアプリケーションが徐々に登場しています。
DPDKバイパスの原則:
この写真は、JingjingWuによるドキュメント「インテル®イーサネットコントローラーX710 / XL710のフロー分岐」から引用されています。
左側は、ネットワークカード->ドライバー->プロトコルスタック->ソケットインターフェイス->ビジネスからの元のデータです。
右側は、UIO(ユーザースペースI / O)バイパスデータに基づくDPDKの方法です。ネットワークカードからのデータ-> DPDKポーリングモード-> DPDK基本ライブラリ->ビジネス
ユーザーモードの利点は、使いやすく、開発と保守が簡単で、柔軟性があります。また、クラッシュは、堅牢なカーネルの動作には影響しません。
DPDKでサポートされるCPUアーキテクチャ:x86、ARM、PowerPC(PPC)
DPDKでサポートされているネットワークカードのリスト:core.dpdk.org/supported/、主流はIntel 82599(光ポート)、Intel x540(電気ポート)を使用
第4に、DPDKUIOの基礎
ドライバーをユーザーモードで実行するために、LinuxはUIOメカニズムを提供します。UIOは、読み取りを介して割り込みを検知し、mmapを介してネットワークカードと通信できます。
UIOの原則:
ユーザーモードドライバーを開発するには、いくつかの手順があります。
- ハード割り込みはカーネルでのみ処理できるため、カーネルで実行されるUIOモジュールを開発します
- / dev / uioXを介した割り込みの読み取り
- mmapを介した周辺機器との共有メモリ
5. DPDKコアの最適化:PMD
DPDKのUIOドライバーは、ハードウェアが割り込みを発行しないように保護し、ユーザーモードでアクティブポーリングを採用します。このモードはPMD(ポーリングモードドライバー)と呼ばれます。
UIOはカーネルをバイパスし、アクティブにポーリングしてハード割り込みを削除するため、DPDKはユーザーモードでパケットの送受信処理を実行できます。ゼロコピーとシステムコールなしの利点をもたらし、同期処理により、コンテキスト切り替えによって引き起こされるキャッシュミスが減少します。
PMDで実行されているコアはユーザーモードCPU100%の状態になります。
ネットワークがアイドル状態の場合、CPUは長時間アイドル状態になり、エネルギー消費の問題が発生します。そのため、DPDKは割り込みDPDKモードを導入しました。
割り込みDPDK:
David Su / Yunhong Jiang / WeiWangによるドキュメント「TowardsLowLatency InterruptModeDPDK」から引用された画像
その原理はNAPIと非常に似ています。つまり、処理するパッケージがない場合はスリープ状態になり、割り込み通知に変わります。また、同じCPUコアを他のプロセスと共有できますが、DPDKプロセスのスケジューリング優先度は高くなります。
6、DPDK高性能コードの実装
1.HugePageを使用してTLBミスを減らします
デフォルトでは、Linuxはページとして4KBを使用します。ページが小さいほど、メモリが大きくなり、ページテーブルのオーバーヘッドが大きくなり、ページテーブルのメモリ使用量が大きくなります。CPUはTLB(Translation Lookaside Buffer)のコストが高いため、通常、数百から数千のページテーブルエントリしか格納できません。プロセスが64Gのメモリを使用する場合、64G / 4KB = 16000000(1600万)ページであり、各ページはページテーブルエントリで16000000 * 4B = 62MBを占有します。HugePageがページとして2MBを使用する場合、64G / 2MB = 2000のみであり、その数は同じレベルではありません。
DPDKは、x86-64で2MBおよび1GBのページサイズをサポートするHugePageを使用します。これにより、ページテーブルエントリのサイズが幾何学的に縮小され、TLB-Missが削減されます。また、Mempool、MBuf、Ring、Bitmapなどの基本的なライブラリも提供します。私たちの慣例によれば、データプレーン(データプレーン)の頻繁なメモリ割り当てと解放では、メモリプールを使用する必要があり、rte_mallocを直接使用することはできません。DPDKのメモリ割り当ての実装は非常に単純で、ptmallocほど良くありません。
2.SNA(シェアードナッシングアーキテクチャ)
ソフトウェアアーキテクチャは分散化されており、グローバル共有は可能な限り回避されます。これにより、グローバルな競争が発生し、水平方向に拡張する機能が失われます。NUMAシステムでは、メモリはノード間でリモートで使用されません。
3. SIMD(単一命令複数データ)
初期のmmx / sseから最新のavx2まで、SIMDの機能が強化されました。DPDKは、バッチを使用して複数のパケットを同時に処理し、次にベクトルプログラミングを使用してすべてのパケットを1サイクルで処理します。たとえば、memcpyはSIMDを使用して速度を上げます。
SIMDはゲームのバックグラウンドでより一般的ですが、他のビジネスでバッチ処理と同様のシナリオがある場合は、パフォーマンスを向上させたい場合にそれが満たされるかどうかも確認できます。
4.遅いAPIを使用しないでください
ここでは、gettimeofdayなどの低速APIを再定義する必要があります。64ビットのvDSOを介してカーネルモードに入る必要はなくなりましたが、これは単なるメモリアクセスであり、1秒あたり数千万レベルに達する可能性があります。 。ただし、10GE未満では、1秒あたりの処理能力が数千万に達することを忘れないでください。したがって、gettimeofdayでさえ遅いAPIです。DPDKは、HPETまたはTSCの実装に基づいて、rte_get_tsc_cyclesインターフェイスなどのCyclesインターフェイスを提供します。
x86-64でRDTSC命令を使用して、レジスタから直接読み取ります。2つのパラメータを入力する必要があります。より一般的な実装:
static inline uint64_t
rte_rdtsc(void)
{
uint32_t lo, hi;
__asm__ __volatile__ (
"rdtsc" : "=a"(lo), "=d"(hi)
);
return ((unsigned long long)lo) | (((unsigned long long)hi) << 32);
}
この方法で記述するロジックは正しいですが、極端ではありません。結果を取得するには2ビット演算も必要です。DPDKがどのように実装されているかを見てみましょう。
static inline uint64_t
rte_rdtsc(void)
{
union {
uint64_t tsc_64;
struct {
uint32_t lo_32;
uint32_t hi_32;
};
} tsc;
asm volatile("rdtsc" :
"=a" (tsc.lo_32),
"=d" (tsc.hi_32));
return tsc.tsc_64;
}
Cのユニオン共有メモリの巧妙な使用、直接割り当て、不要な操作の削減。しかし、tscを使用するときに直面して解決する必要があるいくつかの問題があります
- CPUアフィニティ、不正確なマルチコアビートの問題を解決します
- 不正確な実行の問題を解決するためのメモリバリア
- 周波数の低下を禁止し、Intel Turbo Boostを禁止し、CPU周波数を修正し、周波数の変化によって引き起こされる不正確さの問題を解決します
5.最適化をコンパイルして実行します
分岐予測
最新のCPUは、パイプラインとスーパースカラーを介して並列処理機能を向上させます。並列機能をさらに発揮するために、分岐予測を行い、CPUの並列機能を向上させます。分岐が発生すると、どの分岐を入力できるかを判断し、分岐のコードを事前に処理し、コードやレジスタなどを読み取るために命令を事前に読み取り、予測があれば前処理を破棄します。失敗します。私たちがビジネスを展開するとき、この分岐が真であるか偽であるかをよく知っていることがあります。そのため、手動介入によってよりコンパクトなコードを生成し、CPU分岐予測の成功率を示すことができます。
#pragma once
#if !__GLIBC_PREREQ(2, 3)
# if !define __builtin_expect
# define __builtin_expect(x, expected_value) (x)
# endif
#endif
#if !defined(likely)
#define likely(x) (__builtin_expect(!!(x), 1))
#endif
#if !defined(unlikely)
#define unlikely(x) (__builtin_expect(!!(x), 0))
#endif
CPUキャッシュプリフェッチ
キャッシュミスのコストは非常に高く、メモリを読み戻すのに65ナノ秒かかります。これにより、アクセスするデータをアクティブにプッシュするCPUキャッシュを最適化できます。典型的なシナリオは、リンクリストのトラバーサルです。リンクリストの次のノードはランダムメモリアドレスであるため、CPUに自動的にプリロードしないでください。ただし、このノードを処理しているときは、CPU命令を介して次のノードをキャッシュにプッシュできます。
APIドキュメント:doc.dpdk.org/api/rte__pr .. ..
static inline void rte_prefetch0(const volatile void *p)
{
asm volatile ("prefetcht0 %[p]" : : [p] "m" (*(const volatile char *)p));
}
#if !defined(prefetch)
#define prefetch(x) __builtin_prefetch(x)
#endif
…などなど
メモリアライメント
メモリアライメントには2つの利点があります。
l構造体メンバーがキャッシュラインを越えないようにします。レジスターにマージするために2回の読み取りが必要になり、パフォーマンスが低下します。構造体のメンバーを並べ替えて、最大から最小に整列させる必要があります。「データアライメント:まっすぐに伸ばして右に飛ぶ」を参照してください。
#define __rte_packed __attribute__((__packed__))
lマルチスレッドシナリオでの書き込み時に偽共有が生成され、キャッシュミスが発生し、構造がキャッシュラインに従って整列されます
#ifndef CACHE_LINE_SIZE
#define CACHE_LINE_SIZE 64
#endif
#ifndef aligined
#define aligined(a) __attribute__((__aligned__(a)))
#endif
一定の最適化
定数関連の操作のコンパイルフェーズが完了します。たとえば、C ++ 11ではconstexpが導入されています。たとえば、GCCの__builtin_constant_pを使用して値が定数かどうかを判断し、定数をコンパイルして結果を取得できます。ネットワークシーケンスホストシーケンス変換の例
#define rte_bswap32(x) ((uint32_t)(__builtin_constant_p(x) ? \
rte_constant_bswap32(x) : \
rte_arch_bswap32(x)))
rte_constant_bswap32の実現
#define RTE_STATIC_BSWAP32(v) \
((((uint32_t)(v) & UINT32_C(0x000000ff)) << 24) | \
(((uint32_t)(v) & UINT32_C(0x0000ff00)) << 8) | \
(((uint32_t)(v) & UINT32_C(0x00ff0000)) >> 8) | \
(((uint32_t)(v) & UINT32_C(0xff000000)) >> 24))
CPU命令を使用する
最新のCPUは、ラージエンドからスモールエンドへの変換など、一般的な機能を直接完了するための多くの命令を提供し、x86はbswap命令を直接サポートします。
static inline uint64_t rte_arch_bswap64(uint64_t _x)
{
register uint64_t x = _x;
asm volatile ("bswap %[x]"
: [x] "+r" (x)
);
return x;
}
この実現は、GLIBC、最初の定数最適化、CPU命令最適化の実現でもあり、最終的にはベアコードで実現されます。結局のところ、彼らはすべてトッププログラマーであり、言語、コンパイラー、および実現の追求は異なるため、ホイールを作成する前に、まずホイールを理解する必要があります。
Googleのオープンソースcpu_featuresは、特定のCPUの実行を最適化するために、現在のCPUがサポートする機能を取得できます。高性能プログラミングは無限であり、ハードウェア、カーネル、コンパイラ、および開発言語の理解は深く、時代に対応する必要があります。
セブン、DPDKエコロジー
インターネットのバックグラウンド開発では、DPDKフレームワーク自体が提供する機能は比較的少ないです。たとえば、DPDKを使用するには、ARPやIPレイヤーなどの基本的な機能を実装する必要がありますが、これは開始が困難です。より高いレベルのサービスを使用する場合は、ユーザーモードの伝送プロトコルのサポートも必要です。DPDKを直接使用することはお勧めしません。
完全なエコシステムと強力なコミュニティ(主要メーカーがサポート)を備えた現在のアプリケーション層開発プロジェクトは、FD.io(高速データプロジェクト)、シスコのオープンソースがサポートするVPP、比較的完全なプロトコルサポート、ARP、VLAN、マルチパス、IPv4です。 / v6、MPLSなど。ユーザーモードの伝送プロトコルUDP / TCPにはTLDKがあります。プロジェクトの位置付けからコミュニティのサポートまで、これは比較的信頼性の高いフレームワークです。
Tencent CloudのオープンソースF-Stackも注目に値します。開発はより単純で、POSIXインターフェースを直接提供します。
Seastarも非常に強力で柔軟性があり、カーネルモードとDPDKの両方を自由に切り替えることができます。独自の伝送プロトコルSeastar Native TCP / IP Stackもサポートしています。ただし、Seastarを使用した大規模なプロジェクトは見られません。埋めるピットが増える可能性があります。
GBNゲートウェイプロジェクトは、DPDK開発に基づいて、スタンドアロン20GEのWANゲートウェイとしてL3 / IP層アクセスをサポートする必要があります。
Linux、C / C ++テクノロジー交換グループ:[960994558]いくつかの優れた学習ブック、大企業からのインタビューの質問、および共有する人気のテクノロジー教育ビデオ資料(C / C ++、Linux、Nginx、ZeroMQ、MySQLを含む)をまとめました。 、Redis、fastdfs、MongoDB、ZK、ストリーミングメディア、CDN、P2P、K8S、Docker、TCP / IP、coroutine、DPDKなど)、必要に応じて自分で追加できます!〜