次の方法で共有


ネットワーク待機時間とスループット

ネットワークの最適な使用に関連する 3 つの主要な問題:

  • ネットワーク待機時間
  • ネットワークの飽和状態
  • パケット処理への影響

このセクションでは、RPC の使用を必要とするプログラミング タスクを紹介し、次に、2 つのソリューションを設計します。1 つは不適切な記述と、1 つは適切に記述されています。 その後、両方のソリューションが調査され、ネットワーク パフォーマンスへの影響について説明します。

2 つのソリューションについて説明する前に、次のいくつかのセクションでネットワーク関連のパフォーマンスの問題について説明し、明確にします。

ネットワーク待機時間

ネットワーク帯域幅とネットワーク待機時間は別の用語です。 帯域幅が大きいネットワークでは、低遅延は保証されません。 たとえば、サテライト リンクを通過するネットワーク パスは、スループットが非常に高い場合でも、待機時間が長くなることがよくあります。 サテライト リンクを横断するネットワークラウンドトリップの待機時間が 5 秒以上になるのは珍しくありません。 このような遅延の影響は、サーバーの速度に関係なく、要求の送信、応答の待機、別の要求の送信、別の応答の待機などを行うように設計されたアプリケーションは、パケット交換ごとに少なくとも 5 秒待機します。 コンピュータの速度が上がっているにもかかわらず、衛星伝送とネットワークメディアは、一般的に一定の光の速度に基づいています。 そのため、既存のサテライト ネットワークの待機時間が改善される可能性は低くなります。

ネットワークの飽和

いくつかの飽和は、多くのネットワークで発生します。 飽和させる最も簡単なネットワークは、標準の 56k アナログ モデムなどの低速モデム リンクです。 ただし、1 つのセグメント上の多くのコンピューターとのイーサネット リンクも飽和状態になる可能性があります。 これは、帯域幅が低いワイド エリア ネットワークや、制限されたトラフィックを処理できるルーターやスイッチなど、負荷の大きいリンクについても同様です。 このような場合、ネットワークが最も弱いリンクが処理できるパケット数を超えるパケットを送信すると、パケットがドロップされます。 輻輳を回避するために、ドロップされたパケットが検出されたときに Windows TCP スタックがスケールバックされ、大幅な遅延が発生する可能性があります。

パケット処理への影響

RPC、COM、Windows ソケットなどの上位レベルの環境向けにプログラムを開発する場合、開発者は送信または受信パケットごとにバックグラウンドで実行される作業量を忘れる傾向があります。 ネットワークからパケットが到着すると、ネットワーク カードからの割り込みがコンピューターによって処理されます。 次に、遅延プロシージャ 呼び出し (DPC) がキューに登録され、ドライバーを経由する必要があります。 何らかの形式のセキュリティが使用されている場合は、パケットの暗号化を解除するか、暗号化ハッシュを検証する必要があります。 また、各状態で多数の有効性チェックを実行する必要があります。 その後、パケットは最終的な宛先 (サーバー コード) に到着します。 データの小さなチャンクを多数送信すると、データの小さなチャンクごとにパケット処理のオーバーヘッドが発生します。 1 つの大きなチャンクのデータを送信すると、1 つの大きなチャンクと比較して多数の小さなチャンクの実行コストがサーバー アプリケーションで同じであっても、システム全体で CPU 時間が大幅に短縮される傾向があります。

例 1: 設計が不十分な RPC サーバー

リモート ファイルにアクセスする必要があるアプリケーションを想像してみてください。手元にあるタスクは、リモート ファイルを操作するための RPC インターフェイスを設計することです。 最も簡単な解決策は、ローカル ファイルのスタジオ ファイル ルーチンをミラーすることです。 これを行うと、誤ってクリーンされ、使い慣れたインターフェイスになる可能性があります。 省略形の .idl ファイルを次に示します。

typedef [context_handle] void *remote_file;
... .
interface remote_file
{
    remote_file remote_fopen(file_name);
    void remote_fclose(remote_file ...);
    size_t remote_fread(void *, size_t, size_t, remote_file ...);
    size_t remote_fwrite(const void *, size_t, size_t, remote_file ...);
    size_t remote_fseek(remote_file ..., long, int);
}

これは十分にエレガントに見えますが、実際には、これはパフォーマンス障害のための名誉あるレシピです。 一般的な意見とは対照的に、リモート プロシージャ コールは、呼び出し元と呼び出し先の間のワイヤを使用した単なるローカル プロシージャ コールではありません。

このレシピでパフォーマンスが向上する方法を確認するには、最初から 20 バイトを読み取り、最後から 20 バイトを読み取る 2K ファイルを検討し、このパフォーマンスを確認します。 クライアント側では、次の呼び出しが行われます (簡潔にするために多くのコード パスが省略されています)。

rfp = remote_fopen("c:\\sample.txt");
remote_read(...);
remote_fseek(...);
remote_read(...);
remote_fclose(rfp);

ここで、サーバーが 5 秒のラウンド トリップ時間を持つサテライト リンクによってクライアントから分離されていることを想像してください。 これらの呼び出しは、応答を続行する前に待機する必要があります。これは、このシーケンスを 25 秒実行するための絶対的な最小値を意味します。 40 バイトのみを取得することを考えると、これはパフォーマンスがとんでもなく遅くなります。 このアプリケーションの顧客は激怒するでしょう。

ネットワーク パス内のどこかでルーターの容量が過負荷になっているため、ネットワークが飽和状態になっているとします。 この設計により、セキュリティがない場合 (要求ごとに 1 つ、応答ごとに 1 つ) 少なくとも 10 個のパケットを処理するようにルーターが強制されます。 それも良いではありません。

この設計により、サーバーは 5 つのパケットを受信し、5 つのパケットを送信するように強制されます。 繰り返しますが、あまり良い実装ではありません。

例 2: より優れた設計の RPC サーバー

例 1 で説明したインターフェイスを再設計し、改善できるかどうかを確認しましょう。 このサーバーを本当に適切なものにするには、指定されたファイルの使用パターンに関する知識が必要です。この例では、このような知識は想定されていません。 したがって、これはより適切に設計された RPC サーバーですが、最適に設計された RPC サーバーではありません。

この例では、できるだけ多くのリモート操作を 1 つの操作に折りたたむという考え方です。 最初の試行は次のとおりです。

typedef [context_handle] void *remote_file;
typedef struct
{
    long position;
    int origin;
} remote_seek_instruction;
... .
interface remote_file
{
    remote_fread(file_name, void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
    size_t remote_fwrite(file_name, const void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
}

次の使用例は、すべての操作を読み取りと書き込みに折りたたみます。これにより、同じ操作に対してオプションのオープンと、オプションの close and seek を実行できます。

省略形で記述した場合、この同じ一連の操作は次のようになります。

remote_read("c:\\sample.txt", ..., &rfp, FALSE, NULL);
remote_read(NULL, ..., &rfp, TRUE, seek_to_20_bytes_before_end);

より適切に設計された RPC サーバーを検討する場合、2 番目の呼び出しで、 サーバーはfile_nameNULL であることを確認し、rfp に格納されている開いているファイルを使用します。 その後、シーク命令が表示され、読み取る前にファイル ポインターが最後の 20 バイト前に配置されます。 完了すると、 CloseWhenDone フラグが TRUE に設定されていることを認識し、ファイルを閉じて rfp を閉じます。

待ち時間の長いネットワークでは、この優れたバージョンは完了するまでに 10 秒 (2.5 倍高速) かかり、4 つのパケットのみの処理が必要です。2 はサーバーから受信し、2 はサーバーから送信します。 サーバーが実行する追加の ifs とアンマーシャリングは、他のすべてと比較してごくわずかです。

因果順序が正しく指定されている場合は、インターフェイスを非同期にすることもでき、2 つの呼び出しを並列で送信することもできます。 因果順序が使用されている場合、呼び出しは順番にディスパッチされます。つまり、送受信されるパケットの数が同じであっても、待機時間の長いネットワークでは 5 秒の遅延のみが耐えられます。

これをさらに折りたたむには、構造体の配列を受け取るメソッドを 1 つ作成します。これは、特定のファイル操作を記述する配列の各メンバーです。スキャッター/ギャザー I/O のリモート バリエーション。 このアプローチは、各操作の結果がクライアントでそれ以上の処理を必要としない限り、報われます。つまり、アプリケーションは、読み取られた最初の 20 バイトに関係なく、最後に 20 バイトを読み取ります。

ただし、読み取った後に最初の 20 バイトに対して処理を実行して次の操作を決定する必要がある場合、すべてを 1 つの操作に折りたたむ必要はありません (少なくともすべてのケースでは機能しません)。 RPC の優雅さは、アプリケーションがインターフェイスに両方のメソッドを持ち、必要に応じていずれかのメソッドを呼び出すことができるということです。

一般に、ネットワークが関係する場合は、できるだけ多くの呼び出しを 1 つの呼び出しに結合することをお勧めします。 アプリケーションに 2 つの独立したアクティビティがある場合は、非同期操作を使用して並列で実行します。 基本的に、パイプラインを完全に保ちます。