web-dev-qa-db-ja.com

関数のプログラミングはマルチスレッドの方が速くなります。これは、記述方法が異なるため、またはコンパイル方法が異なるためです。

私は関数型プログラミングの世界に飛び込んでいます。関数型言語の方がマルチスレッド/マルチコアプログラムに適していることを随所に読み続けています。 recursionrandom number など、関数型言語がさまざまなことをどのように行うかは理解していますが、関数型言語でマルチスレッド化が高速であるかどうかはわかりませんコンパイルが異なるため、またはwriteが異なるためです。

たとえば、私はJavaで特定のプロトコルを実装するプログラムを作成しました。このプロトコルでは、2つのパーティが互いに何千ものメッセージを送受信し、それらのメッセージを暗号化して再送信します(そして予想どおり、数千の規模で対処する場合はマルチスレッドが重要ですこのプログラムでは、ロックは必要ありません

Scala(これはJVMを使用します)で同じプログラムを書いた場合、この実装はより高速になりますか?イエスの場合、なぜですか?ライティングスタイルのせいですか?場合is書体のため、Javaにはラムダ式が含まれるため、Javaを使用して同じ結果を得ることができませんでした。ラムダですか?Scalaはコンパイル方法が異なるため、より高速ですか?

64
Aventinus

関数型言語が並列処理に適していると人々が言う理由は、それらが通常は可変状態を回避するという事実によるものです。可変処理可能な状態は、並列処理のコンテキストでは「すべての悪の根」です。これらは、並行プロセス間で共有されると、競合状態に非常に簡単に遭遇します。競合状態の解決には、前述のようにロックと同期のメカニズムが含まれます。これにより、プロセスがお互いに共有リソースを使用するのを待機するときにランタイムオーバーヘッドが発生し、これらのすべての概念がより複雑になる傾向があるため、設計が複雑になります。そのようなアプリケーション内で深くネストされています。

変更可能な状態を回避すると、同期とロックのメカニズムの必要性がそれとともになくなります。関数型言語は通常、変更可能な状態を回避するため、当然ながら並列処理に対してより効率的で効果的です。共有リソースのランタイムオーバーヘッドがなく、通常はそれに伴う設計の複雑さが増すこともありません。

ただし、これはすべて付随的です。 Javaのソリューションでも可変状態(特にスレッド間で共有)が回避される場合)をScalaまたはClojureのような関数型言語に変換しても、元のソリューションでは、ロックおよび同期メカニズムによって発生するオーバーヘッドがすでになくなっているため、同時効率の観点から。

TL; DR:ScalaのソリューションがJavaのソリューションよりも並列処理の方が効率的である場合、それはコードのコンパイル方法またはJVMでの実行方法が原因ではなく、 Javaソリューションはスレッド間で可変状態を共有しており、競合状態を引き起こすか、それらを回避するために同期のオーバーヘッドを追加しています。

98
MichelHenrich

両方の並べ替え。コンパイルが速くなるようにコードを書く方が簡単なので、速度は速くなります。言語を切り替えることで必ずしも速度の違いが生じるわけではありませんが、関数型言語で始めた場合は、おそらくprogrammerの労力でマルチスレッドを実行できたはずです。同じように、命令型言語では速度が犠牲になるスレッド化の間違いをプログラマが行うのははるかに簡単であり、それらの間違いに気づくのははるかに困難です。

その理由は、命令型プログラマーは一般に、すべてのロックフリーのスレッドコードをできるだけ小さなボックスに入れ、できるだけ早くエスケープして、快適で変更可能な同期の世界に戻そうとするからです。速度を犠牲にするほとんどの間違いは、その境界インターフェースで行われます。関数型プログラミング言語では、その境界でミスをすることについてそれほど心配する必要はありません。呼び出しコードのほとんどは、いわば「ボックス内」にもあります。

8
Karl Bielefeldt

関数型プログラミングは、原則として、より高速なプログラムにはなりません。それが作るものはeasier並列および並行プログラミング用です。これには2つの主要なキーがあります。

  1. 変更可能な状態を回避すると、プログラムで問題が発生する可能性のあるものの数が減る傾向があり、並行プログラムではさらにそうなります。
  2. より高いレベルの概念を優先して、共有メモリとロックベースの同期プリミティブを回避すると、コードのスレッド間の同期が簡素化される傾向があります。

ポイント2の優れた例の1つは、Haskellで確定的並列処理非確定的並行処理の間に明確な違いがあることです。 Simon Marlowの優れた本 Parallel and Concurrent Programming in Haskell を引用するより良い説明はありません(引用は Chapter 1 からです):

並列プログラムは、複数の計算ハードウェア(たとえば、いくつかのプロセッサコア)を使用して、より迅速に計算を実行するプログラムです。目的は、計算のさまざまな部分を同時に実行するさまざまなプロセッサに委任することで、より早く答えに到達することです。

対照的に、concurrencyは、複数の制御スレッドがあるプログラム構造化手法です。概念的には、制御のスレッドは「同時に」実行されます。つまり、ユーザーはそれらの効果が交互に表示されます。それらが実際に同時に実行されるかどうかは、実装の詳細です。並行プログラムは、インターリーブ実行を介して単一のプロセッサー上で、または複数の物理プロセッサー上で実行できます。

これに加えて、マーローは決定論の次元も持ち出すと述べています。

関連する違いはdeterministicnondeterministicプログラミングモデルの間です。確定的プログラミングモデルは、各プログラムが1つの結果しか得られないモデルですが、非確定的プログラミングモデルは、実行のいくつかの側面に応じて、結果が異なる可能性があるプログラムを許可します。並行プログラミングモデルは、予測できないタイミングでイベントを発生させる外部エージェントと相互作用する必要があるため、必ずしも非決定的です。非決定論には、いくつかの顕著な欠点があります。ただし、プログラムのテストと推論が著しく困難になります。

並列プログラミングについては、可能であれば、決定論的プログラミングモデルを使用します。目標はより迅速に回答に到達することであるため、プログラムをプロセスでデバッグしにくくすることは避けます。決定論的並列プログラミングは両方の世界で最高です。順次プログラムでテスト、デバッグ、および推論を実行できますが、プログラムを追加すると、プログラムはより高速に実行されます。

Haskellでは、並列性と並行性の機能はこれらの概念を中心に設計されています。特に、他の言語が1つの機能セットとしてグループ化するものは、Haskellは2つに分割されます。

  • Deterministicparallelismの機能とライブラリ。
  • Non-deterministicconcurrencyの機能とライブラリ。

純粋で確定的な計算を高速化しようとしているだけの場合、確定的な並列処理を使用すると、多くの場合非常に簡単になります。多くの場合、次のようなことをします。

  1. 回答のリストを生成する関数を記述します。それぞれの計算は費用がかかりますが、相互にあまり依存していません。これはHaskellであるため、リストはlazy —コンシューマが要求するまで、その要素の値は実際には計算されません。
  2. Strategies ライブラリを使用して、関数の結果リストの要素を複数のコアで並列に使用します。

実際に数週間前に私のおもちゃプロジェクトプログラムの1つでこれを行いました 。プログラムを並列化することは簡単でした。私がしなければならない重要なことは、実際には、「このリストの要素を並列で計算する」(90行目)というコードを追加することでした。より高価なテストケースの一部。

私のプログラムは、従来のロックベースのマルチスレッドユーティリティを使用した場合よりも高速ですか?私はとても疑います。私の場合のきちんとしたことは、わずかな出費で多くの効果を得たことでした。私のコードはおそらく非常に最適ではありませんが、並列化が非常に簡単なため、適切にプロファイリングおよび最適化するよりもはるかに少ない労力で大きなスピードアップが得られました。競合状態のリスクはありません。そしてそれは、関数型プログラミングで「より高速な」プログラムを作成できる主な方法だと私は主張します。

7
sacundim

Haskellでは、修正ライブラリを介して特別な修正可能な変数を取得しない限り、修正は文字通り不可能です。代わりに、関数は、それらの値と同時に必要な変数(遅延計算)を作成し、不要になったときにガベージコレクションを行います。

変更変数が必要な場合でも、通常は予備的に使用し、変更不可能な変数と一緒に使用できます。 (haskellのもう1つのいい点はSTMです。STMはロックをアトミック操作に置き換えますが、これが関数型プログラミングのためだけのものかどうかはわかりません。)通常、プログラムの一部だけを並列化して改善する必要がありますパフォーマンスに関して。

これにより、Haskellでの並列処理が簡単になることが多く、実際、自動化するための取り組みが進行中です。単純なコードの場合、並列処理とロジックを分離することもできます。

また、Haskellでは評価の順序は重要ではないため、コンパイラは評価が必要なものをキューに作成し、利用可能なコアに送信するだけなので、そうでない「スレッド」の束を作成できます。実際に必要になるまでスレッドになります。重要でない評価順序は純粋さの特徴であり、通常は関数型プログラミングが必要です。

さらに読む
Haskellの並列性(HaskellWiki)
「現実世界のHaskell」での並行およびマルチコアプログラミング
Simon MarlowによるHaskellの並列および並行プログラミング

2
PyRulez