大規模なアプリケーションではSTLを回避する必要がありますか?


24

これは奇妙な質問に聞こえるかもしれませんが、私の部署では次のような状況で問題が発生しています。

ここでは、処理できるように、必要に応じて動的にロードし、後でアンロードするサーバーアプリケーションで作業しています。パフォーマンスの問題。

しかし、私たちが使用している関数は、入力パラメーターと出力パラメーターをSTLオブジェクトとして渡しているため、Stack Overflowの回答述べたように、これは非常に悪い考えです。(投稿にはいくつかの±ソリューションとハックが含まれていますが、すべてが非常に堅実に見えるわけではありません。)

明らかに、入力/出力パラメーターを標準のC ++型で置き換え、関数内で一度それらからSTLオブジェクトを作成できますが、これによりパフォーマンスが低下する可能性があります。

1台のPCで処理できないほど大きくなる可能性のあるアプリケーションをビルドすることを検討している場合、STLをテクノロジとしてまったく使用しないでください。

この質問についてのさらなる背景:質問について
いくつかの誤解があるようです:問題は次のとおりです:
私のアプリケーションは作業を完了するために膨大な量のパフォーマンス(CPU、メモリ)を使用しているので、この作業を分割したいと思います(プログラムはすでに複数の関数に分割されているため)アプリケーションからいくつかのDLLを作成し、それらのDLLのエクスポートテーブルにいくつかの関数を配置することはそれほど難しくありません。これにより、次の状況が発生します。

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1はMachine1にインストールされているアプリケーションのインスタンスであり、App_Inst2はMachine2にインストールされている同じアプリケーションのインスタンスです。
DLL1.xはMachine1にインストールされているDLLで、DLL2.xはMachine2にインストールされているDLLです。
DLLx.1は、エクスポートされたfunction1をカバーします。
DLLx.2は、エクスポートされたfunction2をカバーします。

次に、Machine1でfunction1とfunction2を実行します。これによりMachine1がオーバーロードされることがわかっているため、App_Inst2にメッセージを送信して、そのアプリケーションインスタンスにfunction2を実行するように要求します。

function1とfunction2の入出力パラメーターはSTL(C ++標準タイプライブラリ)オブジェクトであり、定期的にApp_Inst1、App_Inst2、DLLx.yの更新を行うことをお客様に期待する場合があります(ただし、すべてではなく、Machine1をアップグレードする場合がありますが、 Machine2ではなく、アプリケーションのみをアップグレードし、DLLはアップグレードせず、逆も同様です...)。明らかに、インターフェース(入力/出力パラメーター)が変更された場合、顧客は完全なアップグレードを強制されます。

ただし、参照されるStackOverflow URLで述べたように、App_Inst1またはDLLの1つを単純に再コンパイルすると、システム全体がバラバラになる可能性があるため、この投稿の元のタイトルは、STL(C ++標準テンプレートライブラリ)大規模アプリケーション用。

これにより、いくつかの質問/疑念を解決したことを願っています。


44
実行可能ファイルのサイズが原因でパフォーマンスの問題が発生していますか?すべてのソフトウェアが同じコンパイラでコンパイルされると仮定するのが現実的かどうか(たとえば、ビルドサーバーで一度に)、または実際に独立したチームに分割するかどうかについて、詳細を追加できますか?
nvoigt

5
基本的に、すべてのC ++プロジェクトが同じコンパイラバージョンで、ソースの一貫したスナップショット(バージョン)からコンパイルされた同じC ++コンパイラ設定でコンパイルされるようにするには、専用ジョブが「ビルドマネージャ」と「リリースマネージャ」である人が必要ですコードなど。通常、これは「継続的統合」のバナーの下で処理されます。オンラインで検索すると、多くの記事やツールが見つかります。時代遅れの慣行は自己強化することができます-1つの時代遅れの実践は時代遅れのすべての実践につながる可能性があります。
rwong

8
リンクされた質問の受け入れられた答えは、問題は一般にC ++呼び出しにあることを示しています。したがって、「C ++でなくSTL」は役に立たないので、安全な側にいるには裸のCを使用する必要があります(しかし、答えも見てください、シリアル化はおそらくより良い解決策です)。
FRAX

52
パフォーマンスの問題対処できるようにするために、必要なときに動的にロードし、その後アン どの「パフォーマンスの問題を」?DLLのようなものをメモリからアンロードすることで修正できるメモリを使いすぎること以外の問題は知りません-それが問題である場合、最も簡単な修正はRAMを追加購入することです。実際のパフォーマンスのボトルネックを特定するために、アプリケーションのプロファイル作成しましたか?これはXYの問題のように聞こえるので、あなたは不特定の「パフォーマンスの問題」があり、誰かがすでに解決策を決めています。
アンドリューヘンレ

4
@MaxBarraclough "The STL"は、C ++標準ライブラリに含まれているテンプレート化されたコンテナおよび関数の別名として完全に受け入れられています。実際、Bjarne StroustrupとHerb Sutterによって書かれたC ++コアガイドラインは、これらについて話すときに「STL」を繰り返し参照しています。それ以上に信頼できるソースを入手することはできません。
ショーンバートン

回答:


110

これは、非常に古典的なXY問題です。

あなたの本当の問題はパフォーマンスの問題です。しかし、あなたの質問は、パフォーマンスの問題が実際にどこから来たのか、プロファイリングや他の評価を行っていないことを明確にします。代わりに、コードをDLLに分割することで問題が魔法のように解決されることを望んでいます(記録的にはそうではありません)。

代わりに、実際の問題を解決する必要があります。複数の実行可能ファイルがある場合、どれがスローダウンの原因であるかを確認します。その間、実際にプログラムがすべての処理時間を費やしており、不適切に構成されたイーサネットドライバーなどではないことを確認してください。その後、コード内のさまざまなタスクのプロファイリングを開始します。高精度タイマーはここであなたの友達です。古典的なソリューションは、コードのチャンクの平均および最悪の処理時間を監視することです。

データが得られたら、問題への対処方法を検討し、最適化する場所を検討できます。


54
「代わりに、コードをDLLに分割することで問題が魔法のように解決されることを望んでいます(記録的にはそうなりません)」-これに対して+1。オペレーティングシステムは、ほぼ確実にデマンドページングを実装します。これにより、DLLの機能のロードおよびアンロードとまったく同じ結果が得られます。手動による介入は必要ありません。OSの仮想メモリシステム(実際にはありそうにない)よりも、コードが1回使用される時間を予測する方が優れている場合でも、OSはDLLファイルをキャッシュし、いずれにしても労力を無効にします
ジュール

@Julesアップデートを参照してください-DLLが別のマシンにのみ存在することを明確にしたため、このソリューションが機能しているのを見ることができます。ただし、通信のオーバーヘッドが発生しているため、確認するのは困難です。
イズカタ

2
@Izkata-まだ完全には明らかではありませんが、説明されているのは、ローカルまたはリモートの各関数のバージョンを(実行時構成に基づいて)動的に選択することです。ただし、特定のマシンで決して使用されないEXEファイルの部分は、メモリにロードされることはないため、この目的でDLLを使用する必要はありません。すべての関数の両方のバージョンを標準ビルドに含め、関数ポインター(またはC ++呼び出し可能オブジェクト、または任意のメソッド)のテーブルを作成して、各関数の適切なバージョンを呼び出します。
ジュール

38

複数の物理マシン間でソフトウェアを分割する必要がある場合、マシン間でデータを渡すときに何らかの形でシリアル化する必要があります。これは、実際にはマシン間で同じ正確なバイナリを送信できる場合があるからです。ほとんどのシリアル化方法では、STLタイプの処理に問題はありません。そのため、大文字小文字は気になりません。

アプリケーションを共有ライブラリ(DLL)に分割する必要がある場合(パフォーマンス上の理由から、パフォーマンスの問題を実際に解決することを確認する必要があります)、STLオブジェクトの受け渡しは問題になる可能性がありますが、そうする必要はありません。既に提供したリンクで説明しているように、同じコンパイラーと同じコンパイラー設定を使用すると、STLオブジェクトの受け渡しが機能します。ユーザーがDLLを提供する場合、これを簡単に当てにできない場合があります。ただし、すべてのDLLを提供し、すべてを一緒にコンパイルすると、DLLを使用してDLL境界を越えてSTLオブジェクトを使用できるようになる可能性が非常に高くなります。オブジェクト所有権を渡す場合、STL固有の問題ではありませんが、複数の異なるヒープを取得しないように、コンパイラー設定に注意する必要があります。


1
はい、特にDLL / soの境界を越えて割り当てられたオブジェクトを渡すことに関する部分。一般的に、複数アロケーターの問題を完全に回避する唯一の方法は、構造を割り当てたDLL / so(またはライブラリ!)もそれを解放することです。これが、多くのCスタイルAPIがこのように書かれている理由です:割り当てられた配列/構造体を返す各APIの明示的な無料API。STLの追加の問題は、呼び出し元が、渡された複雑なデータ構造(要素の追加/削除)を変更できることを期待し、それも許可されないことです。しかし、強制するのは難しいです。
davidbak

1
このようにアプリケーションを分割しなければならなかった場合、おそらくCOMを使用しますが、すべてのコンポーネントが独自のCおよびC ++ライブラリ(同じ場合は共有できますが、必要に応じて分岐できます) 。遷移の中など、私はこれはしかし、OPの問題のための適切な措置であることを確信していない。
サイモン・リヒター

2
具体的な例として、プログラムはどこかで別のマシンにテキストを送信したい可能性が高いです。ある時点で、そのテキストの表現に関係するいくつかの文字へのポインターが存在するようになります。これらのポインターのビットを送信するだけで、受信側で定義された動作を期待することは絶対にできません
-Caleth

20

私たちはここでサーバーアプリケーションで作業していますが、それはますます大きくなっており、それを別の部分(DLL)に分割し、必要に応じて動的にロードし、後でアンロードすることを検討している時点でも、パフォーマンスの問題

RAMは安価であるため、非アクティブなコードは安価です。コードのロードとアンロード(特にアンロード)は脆弱なプロセスであり、最新のデスクトップ/サーバーハードウェアでのプログラムのパフォーマンスに大きな影響を与えることはほとんどありません。

キャッシュはより高価ですが、それは最近アクティブになったコードにのみ影響し、未使用のメモリにあるコードには影響しません。

一般に、プログラムはコードサイズではなくデータサイズまたはCPU時間のためにコンピューターより大きくなります。コードサイズが大きくなりすぎて大きな問題を引き起こしている場合は、まずそれがなぜ起こっているのかを見てみたいと思います。

しかし、使用している関数は、入力および出力パラメーターをSTLオブジェクトとして渡しているため、このStackOverflow URLで述べたように、これは非常に悪い考えです。

dllと実行可能ファイルがすべて同じコンパイラでビルドされ、同じC ++ランタイムライブラリに対して動的にリンクされている限り、問題ありません。したがって、アプリケーションとそれに関連するdllが単一のユニットとしてビルドおよびデプロイされる場合、問題になることはありません。

問題になる可能性があるのは、ライブラリが別の人によって構築された場合、または個別に更新できる場合です。

1台のPCで処理できないほど大きくなる可能性のあるアプリケーションを構築することを検討している場合、STLをテクノロジとしてまったく使用しないでください。

あんまり。

アプリケーションを複数のマシンに分散し始めると、それらのマシン間でデータをどのようにやり取りするかについて、十分な検討が必要になります。STLタイプまたはより基本的なタイプが使用されるかどうかの詳細は、ノイズで失われる可能性があります。


2
そもそも非アクティブなコードがRAMにロードされることはありません。ほとんどのオペレーティングシステムは、実際に必要な場合にのみ実行可能ファイルからページをロードします。
ジュール

1
@Jules:デッドコードがライブコードと混合している場合(ページサイズ= 4kの粒度)、マッピングされてロードされます。キャッシュは非常に細かい(64B)粒度で動作するため、未使用の関数がそれほど害を与えないことは依然としてほとんど真実です。ただし、各ページにはTLBエントリが必要であり、(RAMとは異なり)ランタイムリソースが不足しています。(少なくともLinuxではファイルバックアップマッピングはhugepagesを使用しません。1つのhugepageはx86-64で2MiBであるため、hugepagesでTLBミスを起こすことなく、より多くのコードまたはデータをカバーできます。)
Peter Cordes

1
@PeterCordesの注意事項:したがって、リリースのためのビルドプロセスの一部として「PGO」を使用してください。
JDługosz

13

いいえ、結論が続くとは思いません。プログラムが複数のマシンに分散している場合でも、STLを内部的に使用することでモジュール間/プロセス通信で使用することを強制する理由はありません。

実際、前者は内部で使用されるものに比べて変更がより堅固/困難になるため、外部インターフェイスの設計を最初から内部実装から分離する必要があると主張します


7

あなたはその質問の要点を見逃しています。

基本的に2種類のDLLがあります。あなた自身、そして他の誰かの。「STLの問題」は、あなたと彼らが同じコンパイラを使用していない可能性があることです。明らかに、それはあなた自身のDLLにとって問題ではありません。


5

同じコンパイラとビルドオプションを使用して、同じソースツリーからDLLを同時にビルドすると、正常に機能します。

ただし、アプリケーションを複数の部分に分割する「Windows風味」の方法は、COMコンポーネントです。これらは小さいもの(個別のコントロールまたはコーデック)または大きいもの(IEはmshtml.dllのCOMコントロールとして利用可能です)。

必要なときに動的にロードし、その後アンロードする

サーバーアプリケーションの場合、これは恐らくひどい効率になるでしょう。長期間にわたって複数のフェーズを移動するアプリケーションがある場合にのみ、本当に実行可能になります。そのため、何かが再び必要にならない場合を把握できます。オーバーレイメカニズムを使用するDOSゲームを思い出させます。

また、仮想メモリシステムが正常に動作している場合、未使用のコードページをページアウトすることでこれを処理します。

1台のPCで処理できないほど大きくなる可能性があります

より大きなPCを購入します。

適切な最適化により、ラップトップがhadoopクラスターよりも優れていることを忘れないでください

本当に複数のシステム必要な場合は、シリアル化コストがかかるため、システム間の境界について非常に慎重に検討する必要があります。これは、MPIのようなフレームワークの検討を開始する場所です。


1
「長期間にわたって複数のフェーズを移動するアプリケーションがある場合にのみ、本当に実行可能になり、何かが再び必要とされないことを知ることができます」-それでも、OSが大いに役立つ可能性は低いDLLファイルをキャッシュします。これは、基本実行可能ファイルに関数を直接含めるだけではなく、多くのメモリを消費する可能性があります。オーバーレイは、仮想メモリのないシステムでのみ、または仮想アドレス空間が制限要因である場合にのみ有用です(このアプリケーションは32ビットではなく64ビットだと思います)。
ジュール

3
「より大きなPCを購入する」+1。複数テラバイトのRAMを搭載したシステムを取得できるようになりました。1人の開発者の1時間未満の料金でAmazonから1人を雇うことができます。メモリ使用量を削減するためにコードを最適化するために、開発者はどれくらいの時間を費やしますか?
ジュール

2
「より大きなPCを購入する」ことで私が直面した最大の問題は、「アプリの規模はどこまで拡大するか」という質問に関連していました。私が答えたのは、「テストにいくら費やしますか?適切なマシンをレンタルし、適切な大規模なテストをセットアップするのに何千ドルもかかるほどの規模になると予想しているからです。シングルCPU PCでできること」。多くの高齢プログラマーは、PCがどれだけ成長したかという現実的な考えを持っていません。現代のPCのビデオカードだけが20世紀の標準ではスーパーコンピューターです。
MSalters

COMコンポーネント?たぶん1990年代に、しかし今?
ピーターモーテンセン

@MSalters-正しい... 1台のPCでアプリケーションをどれだけ拡張できるかについて質問がある場合は、Amazon EC2 x1e.32xlargeインスタンスタイプの仕様を確認する必要があります。 2.3GHz(3.1GHzまでバースト可能)、潜在的に340GB / sのメモリ帯域幅(仕様に記載されていないメモリの種類によって異なります)、および3.9TiBのRAM。メインRAMに触れることなく、ほとんどのアプリケーションを実行するのに十分なキャッシュがあります。でも、GPUなしで、それは2000年から500ノードのスーパーコンピュータクラスタなどの強力なようだ
ジュール

0

ここでは、処理できるように、必要に応じて動的にロードし、後でアンロードするサーバーアプリケーションで作業しています。パフォーマンスの問題。

最初の部分は理にかなっています(パフォーマンス上の理由から、アプリケーションを異なるマシンに分割する)。

2番目の部分(ライブラリのロードとアンロード)は意味がありません。これは追加の努力であり、(実際に)改善されることはないためです。

あなたが説明している問題は、専用の計算機でよりよく解決されますが、これらは同じ(メイン)アプリケーションで動作するべきではありません。

従来のソリューションは次のようになります。

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

フロントエンドマシンと計算マシンの間には、ロードバランサーやパフォーマンスモニタリングなどの余分なものがある場合があり、専用マシンで特殊な処理を維持することは、キャッシュとスループットの最適化に適しています。

これは、DLLの追加のロード/アンロード、またはSTLとの関係を意味するものではありません。

つまり、必要に応じて内部でSTLを使用し、要素間でデータをシリアル化します(grpcおよびプロトコルバッファーとそれらが解決する問題の種類を参照)。

これは、あなたが提供した限られた情報では、これは古典的なxy問題のように見えます(@Grahamが言ったように)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.