マルチスレッドで関数型プログラミングは速くなりますか?物事の書き方が違うのか、物事のコンパイルが違うのですか?


63

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

たとえば、特定のプロトコルを実装するプログラムをJavaで作成しました。このプロトコルでは、2者は互いに何千ものメッセージを送受信し、それらのメッセージを暗号化し、何度も再送信(および受信)します。予想どおり、数千単位の規模を扱う場合、マルチスレッドが重要です。このプログラムには、ロックは含まれていません

JVMを使用するScalaで同じプログラムを作成すると、この実装は高速になりますか?はいの場合、なぜですか?文章のスタイルが原因ですか?Javaがラムダ式を含むようになったため、書き込みスタイル原因である場合、ラムダを使用したJavaを使用して同じ結果を達成できませんでしたか?または、Scalaが異なる方法でコンパイルするため、高速ですか?


64
Afaik関数型プログラミングはマルチスレッドを高速化しません。この点で役立つ不変性や副作用のない関数などの関数型プログラミングの機能があるため、マルチスレッドの実装がより簡単で安全になります。
ピーターB

7
1)より良いとは実際には定義されていないことに注意してください2)それは確かに 単に「より速い」と定義されていないことに注意してください。Yに対して0.1%のパフォーマンス向上のためにコードの10億倍のサイズを必要とする言語Xは、より適切な定義のためにYよりも優れていません。
バクリウ

2
「関数型プログラミング」または「関数型スタイルで書かれたプログラム」について尋ねるつもりでしたか?多くの場合、プログラミングを高速化しても、プログラムは高速化されません。
ベンフォークト

1
そこにバックグラウンドで実行して、割り当ての要求に追いつくために持っているGCは常にだ...と私はそれが...マルチスレッドだかわからないことを忘れないでください
Mehrdad

4
ここで最も簡単な答えは、関数型プログラミングでは競合状態の問題が少ないプログラムを書くことができますが、命令型スタイルで書かれたプログラムが遅くなるということではありません。
ダウィッドプラ

回答:


97

関数型言語が並列処理に適していると人々が言うのは、通常、可変状態を避けるためです。可変状態は、並列処理のコンテキストにおける「すべての悪の根」です。並行プロセス間で共有されると、競合状態に陥りやすくなります。競合状態の解決には、前述のようにロックおよび同期メカニズムが含まれます。これらのメカニズムはすべて、共有リソースを利用するためにプロセスが互いに待機するため、実行時のオーバーヘッドを引き起こします。そのようなアプリケーション内で深くネストされています。

可変状態を回避すると、同期およびロックメカニズムの必要性がなくなります。関数型言語は通常、可変状態を回避するため、当然、並列処理にとってより効率的かつ効果的です。共有リソースの実行時オーバーヘッドは発生せず、通常続く設計の複雑さもありません。

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

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


2
1つのスレッドのみがデータの一部を変更する場合; 特別な注意は必要ありません。複数のスレッドが同じデータを変更する場合にのみ、何らかの特別な注意(同期、トランザクションメモリ、ロックなど)が必要です。この例としては、スレッドのスタックがあります。これは、機能コードによって常に変化しますが、複数のスレッドによって変更されることはありません。
ブレンダン

31
1つのスレッドがデータを変更し、他のスレッドがそれを読み取るだけで、「特別な注意」を払わなければなりません。
ピーターグリーン

10
@Brendan:いいえ、他のスレッドが同じデータから読み込んでいる間に1つのスレッドがデータを変更すると、競合状態になります。1つのスレッドのみが変更している場合でも、特別な注意が必要です。
Cornstalks

3
可変状態は、並列処理のコンテキストでは「すべての悪の根」です。Rustをまだ見ていない場合は、それを覗いてみることをお勧めします。真の問題はエイリアスと可変であり、エイリアスのみを持っているか、可変性しか持っていない場合、問題はありません。
マチューM.

2
なつみ そう、ありがとう!答えの中で物事をより明確に表現するために編集しました。可変状態は、並行プロセス間で共有されている場合にのみ「すべての悪の根」です-所有権制御メカニズムでRustが回避すること。
ミシェルヘンリッヒ

8

両方の並べ替え。より速くコンパイルしやすい方法でコードを書くのが簡単だからです。言語を切り替えても必ずしも速度の違いが得られるわけではありませんが、関数型言語で始めたのであれば、おそらくプログラマの労力を大幅に削減してマルチスレッドを実行できたはずです。同じ方針に沿って、プログラマーにとっては、命令型言語では速度を犠牲にするスレッド化のミスを犯すのはずっと簡単であり、それらのミスに気付くのははるかに困難です。

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


7

一般的なルールとして、関数型プログラミングはより高速なプログラムを作成しません。それにより、並列および並行プログラミングが容易になります。これには2つの主要なキーがあります。

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

ポイント#2の一つの優れた例は、Haskellで、我々は明確に区別持っていることである決定論的並列処理非決定論的同時実行を。Simon Marlowの著書 『Haskellでの並列および並行プログラミング』を引用するよりも良い説明はありません(引用は第1章からです)。

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

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

これに加えて、Marlowは、決定論の次元も取り上げています。

関連する区別は、確定プログラミングモデルと確定プログラミングモデルです。決定論的プログラミングモデルは、各プログラムが1つの結果のみを提供できるモデルです。一方、非決定論的プログラミングモデルは、実行のいくつかの側面に応じて異なる結果をもたらす可能性があるプログラムを受け入れます。同時プログラミングモデルは、予測できない時間にイベントを発生させる外部エージェントと対話する必要があるため、必然的に非決定的です。ただし、非決定性にはいくつかの顕著な欠点があります。プログラムのテストと推論が非常に難しくなります。

並列プログラミングでは、可能な限り決定論的プログラミングモデルを使用します。目標は、より迅速に答えに到達することであるため、プロセスでデバッグを難しくすることは避けたいと考えています。決定論的並列プログラミングは両方の長所です。テスト、デバッグ、推論はシーケンシャルプログラムで実行できますが、プロセッサを追加するとプログラムは高速に実行されます。

Haskellでは、これらの概念に基づいて並列処理と同時実行機能が設計されています。特に、他の言語が1つの機能セットとしてグループ化されている場合、Haskellは2つに分割されます。

  • 並列性のための決定論的な機能とライブラリ。
  • 並行性のための非決定的機能とライブラリ。

純粋な決定論的計算を高速化しようとしている場合、決定論的並列処理を使用すると、多くの場合、作業がはるかに簡単になります。多くの場合、次のようなことを行うだけです。

  1. 回答のリストを作成する関数を作成します。それぞれの回答は計算に費用がかかりますが、互いにあまり依存しません。これはHaskellなので、リストは遅延します。要素の値は、消費者が要求するまで実際には計算されません。
  2. Strategiesライブラリを使用して、関数の結果リストの要素を複数のコアにわたって並行して使用します。

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

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


2

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

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

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

また、Haskellでは評価の順序は重要ではないという事実により、コンパイラーは、評価が必要なキューを作成し、利用可能なコアに送信するので、実際に必要になるまでスレッドになります。重要ではない評価順序は純度の特性であり、通常は関数型プログラミングが必要です。

また、読書
Haskellでは並列処理(HaskellWiki)
、「現実世界ハスケル」で並行してマルチコアプログラミング
サイモン・マーローによるHaskellでは並列並行プログラミング


7
grep java this_postgrep scala this_postそしてgrep jvm this_post:)何も結果を返さない
アンドレスF.

4
質問はあいまいです。タイトルと最初の段落では、関数型プログラミング全般について質問しています。2番目と3番目の段落では、特に JavaとScalaについて質問しています。それは残念です。特に、Scalaの核となる強みの1つは、まさに関数型言語ではないという事実です。Martin Oderskyは「事後機能」と呼び、他は「オブジェクト機能」と呼びます。「関数型プログラミング」という用語には2つの異なる定義があります。一つは、「ファーストクラスの手順を使用したプログラミング」(LISPに適用される元の定義)、他方は...です
イェルクWミッターク

2
「参照透過的で純粋な副作用のない関数と不変の永続データを使用したプログラミング」(より厳密で、より新しい解釈)。この答えは、a)最初の解釈は並列性と並行性とはまったく関係がないため、b)最初の解釈は基本的に無意味になったため、理にかなっている2番目の解釈に対応しています。今日は(Javaのを含む)、ファーストクラスの手続きを持ち、そしてc)OPは、JavaとScalaの違いについて尋ねるが、ない...ありません
イェルクWミッターク

2
定義#1に関する2つの間、定義#2のみ。
ヨルグWミットタグ

評価の内容は、ここに記載されているとおりではありません。既定では、ランタイムはマルチスレッドをまったく使用せず、IIRCを有効にしても、マルチスレッドを有効にする場合でも、ランタイムに並列で評価するものを伝える必要があります。
キュービック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.