web-dev-qa-db-ja.com

共有メモリとメッセージパッシングは大きなデータ構造をどのように処理しますか?

GoとErlangの並行性へのアプローチを見ると、どちらもメッセージパッシングに依存していることに気づきました。

このアプローチでは、共有状態がないため、複雑なロックの必要性が明らかに軽減されます。

ただし、接尾辞配列のように、メモリ内の単一の大きなデータ構造への並列読み取り専用アクセスを必要とする多くのクライアントの場合を考えてみてください。

私の質問:

  • データは読み取り専用であり、単一の場所にのみ存在する必要があるため、ロックはほとんど不要であるため、共有状態の使用はメッセージパッシングよりも高速でメモリの使用量が少なくなりますか?

  • メッセージパッシングのコンテキストでこの問題にどのようにアプローチしますか?データ構造にアクセスできる単一のプロセスがあり、クライアントはそれからデータを順番に要求するだけでよいでしょうか?または、可能であれば、データをチャンク化して、チャンクを保持する複数のプロセスを作成しますか?

  • 最新のCPUとメモリのアーキテクチャを考えると、2つのソリューションの間に大きな違いがありますか?つまり、共有メモリを複数のコアで並列に読み取ることができますか?つまり、両方の実装をほぼ同じように実行するハードウェアのボトルネックはありませんか?

57
wsorenson
  • はい、この場合、共有状態の方が高速になる可能性があります。ただし、ロックを解除できる場合にのみ、これは完全に読み取り専用である場合にのみ実行できます。 「ほとんどが読み取り専用」の場合は、ロックが必要です(ロックのない構造を記述できない限り、ロックよりもトリッキーであることに注意してください)。そうすると、次のように実行するのが難しくなります。優れたメッセージパッシングアーキテクチャとして高速です。

  • はい、それを共有するために「サーバープロセス」を書くことができます。非常に軽量なプロセスでは、データにアクセスするための小さなAPIを作成するよりも重くはありません。データを「所有する」オブジェクト(OOPの意味で)のように考えてください。並列処理を強化するためにデータをチャンクに分割すること(DBサークルでは「シャーディング」と呼ばれます)は、大きなケース(またはデータが低速ストレージにある場合)に役立ちます。

  • NUMAが主流になりつつある場合でも、NUMAセルあたりのコア数はますます増えています。また、大きな違いは、メッセージを2つのコア間で渡すことができる一方で、すべてのコアのキャッシュからロックをフラッシュする必要があるため、セル間のバスレイテンシに制限されることです(RAMアクセスよりもさらに低速) )。どちらかといえば、共有状態/ロックはますます実行不可能になっています。

要するに....メッセージパッシングとサーバープロセスに慣れてください、それはすべての怒りです。

編集:この回答をもう一度見て、Goのドキュメントにあるフレーズについて追加したいと思います。

通信によってメモリを共有します。メモリを共有して通信しないでください。

アイデアは次のとおりです。スレッド間で共有されるメモリブロックがある場合、同時アクセスを回避する一般的な方法は、ロックを使用して調停することです。 Goスタイルは、参照を使用してメッセージを渡すことです。スレッドは、メッセージを受信したときにのみメモリにアクセスします。それは、プログラマーの規律のある程度に依存しています。ただし、非常に見栄えの良いコードになり、簡単に校正できるため、デバッグは比較的簡単です。

利点は、すべてのメッセージで大きなデータブロックをコピーする必要がなく、一部のロック実装のようにキャッシュを効果的にフラッシュする必要がないことです。そのスタイルがより高性能なデザインにつながるかどうかを言うのはまだ少し早いです。 (特に、現在のGoランタイムはスレッドのスケジューリングにややナイーブなので)

27
Javier

認識すべきことの1つは、Erlang同時実行モデルは[〜#〜] not [〜#〜]実際には、メッセージ内のデータをプロセス間でコピーする必要があることを指定し、メッセージの送信のみであると述べていることです。通信する方法と共有状態がないこと。すべてのデータは不変であり、is基本であるため、実装はデータをコピーせず、データへの参照を送信するだけの場合があります。または、両方の方法を組み合わせて使用​​することもできます。いつものように、最良のソリューションはなく、その方法を選択する際にはトレードオフが必要です。

BEAMは、参照を送信する大きなバイナリを除いて、コピーを使用します。

28
rvirding

Erlangでは、すべての値が不変です。したがって、メッセージをプロセス間で送信するときにメッセージをコピーする必要はありません。メッセージはとにかく変更できないためです。

Goでは、メッセージの受け渡しは慣例によるものです。チャネルを介して誰かにポインタを送信し、ポイントされたデータを変更することを妨げるものは何もありません。慣例だけなので、もう一度メッセージをコピーする必要はありません。

12
Nick Johnson

最新のプロセッサのほとんどは、 MESIプロトコル のバリアントを使用しています。共有状態のため、異なるスレッド間で読み取り専用データを渡すことは非常に安価です。ただし、このキャッシュラインを格納する他のすべてのキャッシュはデータを無効にする必要があるため、変更された共有データは非常にコストがかかります。

したがって、読み取り専用データがある場合は、メッセージと一緒にコピーする代わりに、スレッド間でデータを共有する方が非常に安価です。ほとんどの場合データを読み取る場合、アクセスを同期する必要があることと、書き込みによって共有データのキャッシュフレンドリーな動作が破壊されることもあり、スレッド間で共有するのにコストがかかる可能性があります。

不変のデータ構造 ここで有益な場合があります。実際のデータ構造を変更する代わりに、古いデータのほとんどを共有する新しいデータ構造を作成するだけですが、変更が必要なものは変更する必要があります。すべてのデータが不変であるため、単一バージョンを共有することは安価ですが、それでも効率的に新しいバージョンに更新できます。

11
Greg Rogers

メッセージパッシングは共有状態を使用できるため、質問は技術的に無意味であることに注意してください。共有状態を回避するために、ディープコピーを使用したメッセージパッシングを意味すると想定します(Erlangが現在行っているように)。

データは読み取り専用であり、単一の場所にのみ存在する必要があるため、ロックはほとんど不要であるため、共有状態の使用はメッセージパッシングよりも高速でメモリの使用量が少なくなりますか?

共有状態を使用すると、lot高速になります。

メッセージパッシングのコンテキストでこの問題にどのようにアプローチしますか?データ構造にアクセスできる単一のプロセスがあり、クライアントはそれからデータを順番に要求するだけでよいでしょうか?または、可能であれば、データをチャンク化して、チャンクを保持する複数のプロセスを作成しますか?

どちらのアプローチも使用できます。

最新のCPUとメモリのアーキテクチャを考えると、2つのソリューションの間に大きな違いがありますか?つまり、共有メモリを複数のコアで並列に読み取ることができますか?つまり、両方の実装をほぼ同じように実行するハードウェアのボトルネックはありませんか?

コピーはキャッシュに適さないため、メインメモリである共有リソースの競合を悪化させるため、マルチコアのスケーラビリティが破壊されます。

最終的に、Erlangスタイルのメッセージパッシングは並行プログラミング用に設計されていますが、スループットパフォーマンスに関する質問は実際には並列プログラミングを対象としています。これらは2つのまったく異なる主題であり、実際にはそれらの間の重複はごくわずかです。具体的には、レイテンシーは通常、並行プログラミングのコンテキストでのスループットと同じくらい重要であり、Erlangスタイルのメッセージパッシングは望ましいレイテンシープロファイル(つまり、一貫して低いレイテンシー)を達成するための優れた方法です。共有メモリの問題は、リーダーとライター間の同期ではなく、低遅延のメモリ管理です。

5
Jon Harrop

largeデータ構造とは何ですか?

大きい人は小さい人です。

先週私は2人と話をしました-1人は「大」という言葉を使った組み込みデバイスを作っていました-私は彼にそれが何を意味するのか尋ねました-彼は256キロバイト以上と言います-同じ週の後半に男がメディア配信について話していました-彼は「大きい」という言葉私は彼に何を意味するのか尋ねました-彼は少し考えて、「1台のマシンに収まらない」と言って20〜100TBytesと言いました

Erlangの用語では、「大きい」は「RAMに収まらない」ことを意味する可能性があります-したがって、4GバイトのRAMデータ構造> 100メガバイトは大きいと見なされる可能性があります-500メガバイトのデータ構造をコピーすると問題:小さなデータ構造(たとえば<10メガバイト)のコピーは、Erlangでは決して問題になりません。

非常に大きなデータ構造(つまり、1台のマシンに収まらないもの)をコピーして、複数のマシンに「ストライピング」する必要があります。

だから私はあなたが次を持っていると思います:

小さなデータ構造は問題ありません-データ処理時間が小さいため、コピーが高速です(小さいという理由だけで)

ビッグデータ構造は問題であり、1台のマシンに収まらないため、コピーが不可欠です。

4
ja.

現時点での問題は、ロックとキャッシュラインの一貫性が、より単純なデータ構造(たとえば、数百バイト)をコピーするのと同じくらい高価になる可能性があることです。

ほとんどの場合、ロックのほとんどを排除しようとする巧妙に作成された新しいマルチスレッドアルゴリズムは、常に高速になります。最新のロックフリーデータ構造を使用すると、はるかに高速になります。特に、Sunのナイアガラチップレベルマルチスレッドのような適切に設計されたキャッシュシステムがある場合。

システム/問題がいくつかの単純なデータアクセスに簡単に分解されない場合は、問題があります。また、メッセージパッシングですべての問題を解決できるわけではありません。テラバイトの共有RAMで、同じ共有メモリで最大128個のCPUが動作しているため、Itaniumベースのスーパーコンピューターがまだ販売されているのはこのためです。これらは、同じCPUパワーを備えた主流のx86クラスターですが、データを分解する必要はありません。

これまで言及されていないもう1つの理由は、マルチスレッドを使用すると、プログラムの作成と保守がはるかに簡単になる可能性があることです。メッセージパッシングとシェアードナッシングアプローチにより、さらに保守が容易になります。

一例として、Erlangは物事を高速化するようには設計されていませんが、代わりに多数のスレッドを使用して複雑なデータとイベントフローを構造化します。

これがデザインのポイントのひとつだったと思います。グーグルのウェブの世界では、クラウドで並行して実行できる限り、通常はパフォーマンスを気にしません。また、メッセージパッシングを使用すると、理想的には、ソースコードを変更せずにコンピュータを追加できます。

3
Lothar

ここで紹介されていないソリューションの1つは、マスタースレーブレプリケーションです。大規模なデータ構造がある場合は、そのコピーに対して更新を実行するすべてのスレーブに変更を複製できます。

これは、非常に人工的な設定(リモートコンピュータのメモリから読み取り/書き込みを行うブロックデバイスのmmap?)なしでメモリを共有する可能性すらない複数のマシンに拡張したい場合に特に興味深いものです。

それの変形は、複製されたデータ構造を更新するようにうまく要求するトランザクションマネージャーを持つことであり、それはそれが唯一の更新要求を同時に処理することを確実にします。これは、「大規模なデータ構造」と見なされる、mnesiaテーブルデータのマスター-マスターレプリケーションのmnesiaモデルに近いものです。

3
Christian

通常、メッセージパッシング言語(これは不変の変数があるため、erlangでは特に簡単です)は、プロセス間の実際のデータコピーを最適化します(もちろん、ローカルプロセスのみ:ネットワークの分散パターンを賢く考える必要があります)。それほど問題ではありません。

1
glenda

他の同時パラダイムは、STM、ソフトウェアトランザクショナルメモリです。 Clojureのrefは多くの注目を集めています。 Tim Brayは、erlangとclojureの並行メカニズムを探求する優れたシリーズを持っています

http://www.tbray.org/ongoing/When/200x/2009/09/27/Concur-dot-next

http://www.tbray.org/ongoing/When/200x/2009/12/01/Clojure-論文

0
Gene T