関数型プログラミングは、コンピューティングにおけるマルチコアの傾向を利用するのに適しているとどこかで読んだことがあります。よくわかりませんでした。ラムダ計算とフォン・ノイマンのアーキテクチャに関連していますか?
9 に答える
関数型プログラミングは、副作用を最小化または排除するため、分散プログラミングにより適しています。つまり、マルチコア処理です。
言い換えれば、他のプログラミング スタイルとほぼ同じように、1 つの操作が別の操作に影響を与えることを心配することなく、パズルの多くのピースを個別のコアで同時に解決することができます。
並列処理を扱う上で最も難しいことの 1 つは、破損を防ぐためにデータ構造をロックすることです。2 つのスレッドがデータ構造を完全にロックせずに一度に変更すると、無効なデータからデッドロックまでの結果が生じる可能性があります。
対照的に、関数型プログラミング言語は不変データを強調する傾向があります。状態はロジックとは別に保持され、データ構造が作成されると変更できなくなります。ロックの必要性が大幅に減少します。
もう 1 つの利点は、反復など、非常に簡単に並列化できる一部のプロセスが関数に抽象化されることです。C++ では、リスト内の各項目に対して何らかのデータ処理を実行する for ループがある場合があります。しかし、コンパイラーは、これらの操作が安全に並行して実行できるかどうかを知る方法がありません。おそらく、ある操作の結果は、その前の操作に依存します。map()
orのような関数reduce()
が使用されると、コンパイラは呼び出し間に依存関係がないことを知ることができます。したがって、複数のアイテムを同時に処理できます。
関数型プログラミングがコンピューティングのマルチコアトレンドを利用するのに適していることをどこかで読んだことがあります...私は実際にはその考えを理解していませんでした。ラムダ計算とフォンノイマンアーキテクチャに関連していますか?
あなたが引用した信念の背後にある議論は、純粋関数型プログラミングが副作用を制御し、並列処理の導入をはるかに簡単かつ安全にすることです。したがって、純粋関数型プログラミング言語はマルチコアコンピューターのコンテキストで有利であるはずです。
残念ながら、この信念はいくつかの理由で反証されてから長い間続いていました。
純粋に機能的なデータ構造の絶対的なパフォーマンスは不十分です。したがって、純粋関数型プログラミングは、パフォーマンスのコンテキストでの間違った方向への大きな最初のステップです(これが並列プログラミングの唯一の目的です)。
純粋に機能するデータ構造は、アロケータ/ GCやメインメモリ帯域幅などの共有リソースに負荷をかけるため、拡張性が低くなります。そのため、並列化された純粋に機能するプログラムでは、コアの数が増えるとスピードアップが不十分になることがよくあります。
純粋関数型プログラミングは、パフォーマンスを予測不可能にします。したがって、実際の純粋に機能するプログラムでは、粒度が事実上ランダムであるため、並列化するとパフォーマンスが低下することがよくあります。
たとえば、Haskellコミュニティでよく引用されるろくでなしの2行クイックソートは、通常、F#などの従来の言語で記述された実際のインプレースクイックソートよりも数千倍遅く実行されます。さらに、エレガントなHaskellプログラムを簡単に並列化できますが、不要なコピーがすべてシングルコアでマルチコアマシンのメインメモリ帯域幅全体を飽和させ、並列処理を無価値にするため、パフォーマンスの向上は見られません。実際、Haskellで競争力のある一般的な並列ソートを作成した人は誰もいません。Haskellの標準ライブラリによって提供される最先端のソートは、通常、従来の代替ライブラリよりも数百倍遅くなります。
ただし、ファーストクラス関数の使用を強調するスタイルとしての関数型プログラミングのより一般的な定義は、このパラダイムが並列プログラムの因数分解に理想的であるため、マルチコアプログラミングのコンテキストで実際に非常に役立つことがわかります。たとえば、 .NET4の名前空間からの新しい高階Parallel.For
関数を参照してください。System.Threading.Tasks
副作用がない場合、評価の順序は重要ではありません。その後、式を並列に評価できます。
基本的な議論は、関数がグローバル変数を設定できるため、C/C++/etc のような言語を自動的に並列化するのは難しいということです。次の 2 つの関数呼び出しを検討してください。
a = foo(b, c);
d = bar(e, f);
foo と bar には共通の引数がなく、一方が他方の戻りコードに依存していませんが、foo は bar が依存するグローバル変数 (またはその他の副作用) を設定する可能性があるため、依存関係がある可能性があります。
関数型言語は、foo と bar が独立していることを保証します。つまり、グローバルも副作用もありません。したがって、foo と bar は、プログラマーの介入なしに、異なるコアで自動的に安全に実行できます。
上記のすべての回答は、「可変ストレージを共有しない」ことがプログラムの一部を並列実行するための重要な要素であるという重要な考えに基づいています。並行して実行するものを見つけるという同様に困難な問題は、実際には解決されません。しかし、関数型言語の典型的なより明確な機能表現により、逐次式から並列処理を抽出することが理論的に容易になります。
実際には、ガベージ コレクションとコピー オン チェンジ セマンティクスに基づく言語の「共有可変ストレージがない」というプロパティにより、スレッドの追加が容易になると思います。最良の例はおそらく、機能に近いセマンティクスと明示的なスレッドを組み合わせた Erlang です。
これは少し漠然とした質問です。マルチコア CPU の利点の 1 つは、機能するプログラムを実行して、マシンが実行している他の機能に関係する処理に影響を与えることを心配することなく、シリアルに接続できることです。
サーバーまたは PC のマルチ U サーバーとマルチコア CPU の違いは、同じバス上にあることで得られる速度の節約であり、コアへの通信がより適切かつ高速になります。
編集: 複数のコアの有無にかかわらず、私が行っているほとんどのスクリプト作成では、スクリプトで複数の小さなスクリプトを一度に実行するなど、ハック的な並列処理によってデータを取得する際に問題が発生することはめったにないと言って、この投稿を修飾する必要があります。 URL が読み込まれるのを待つなどの理由で速度が低下することはありません。
二重編集: さらに、多くの関数型プログラミング言語は、何十年もの間、並列バリアントをフォークしてきました。これらは並列計算を利用して速度を向上させますが、実際に普及することはありませんでした。
Joe Armstrong ( Erlangの作成者) による本Programming Erlang: Software for a Concurrent Worldは、マルチコア (/マルチプロセッサ) システムでのErlangの使用についてかなり述べています。ウィキペディアの記事には次のように記載されています。
プロセスの作成と管理は Erlang では些細なことですが、ほとんどの言語ではスレッドは複雑でエラーが発生しやすいトピックと見なされています。Erlang ではすべての並行性が明示的ですが、プロセスは共有変数の代わりにメッセージ パッシングを使用して通信するため、ロックの必要がなくなります。
技術的/科学的用語を省略する理由は、機能的なプログラムがデータを共有しないためです。データは機能間でコピーおよび転送されるため、アプリケーション内に共有データはありません。
また、共有データは、マルチスレッドに関する問題の半分を引き起こします。