1. 概要

パフォーマンスと処理速度は、ソフトウェアの大部分にとって非常に重要です。 その結果、並列計算複雑なタスクを最小限のサブタスクに分割して同時に計算するプロセスは、ソフトウェアエンジニアリングの中心的な概念になりました。

コンピュータサイエンスの分野では、並列処理は新しいトピックではありません。 並列処理は、最初の機械機械から研究されており、既知のトランジスタベースのプロセッサに精力的に適用されています。

Scalaプログラミング言語は、並列コンピューティングを迅速に実装するためのコレクション ParallelCollectionsをすでに提供しています。

このチュートリアルでは、Scalaを使用した並列処理の概念と並列コレクションの使用法を確認します。

2. 並列処理の概要

並列処理には効率が主な目標であることを理解することが重要です。 既存のハードウェアアーキテクチャを使用して、複数の粒度やアーキテクチャからさまざまなコンピューティングを取得します。

つまり、ハードウェアの使用を最適化し、サブプロセスを分割すると精度に直接影響し、論理的な課題につながるため、並列処理は複雑です。

並列処理には、ビットレベル、命令レベル、およびタスクレベルのいくつかのレベルがあります。 ビットレベルと命令レベルはハードウェアアーキテクチャが並列処理を行う方法を示し、タスクレベルはコード命令を扱います。

並列処理もハードウェアに大きく関係しています。 処理装置が命令を処理する方法は、並列処理機能に直接影響します。 マルチコアプロセッサ、マルチプロセッサ、グラフィックプロセスユニット(GPU)、およびコンピュータクラスタは、適用される既知のアーキテクチャです。

強調する価値があるのは、コンピュータークラスターの並列処理を調べるときに、クラスターを介してビッグデータ処理を実行するためにScalaで記述されたフレームワーク ApacheSparkがあります。

2.1. 並列制限

並列処理は重要なようですが、並列処理が不可能な場合があります。 いくつかは、副作用のある操作、非連想操作、競合状態などです。

副作用のある操作とは、変更される複数のスレッドが競合する可能性のある可変状態の操作です。 たとえば、外部変数にアクセスする反復を実行します。 各スレッドは同時に評価でき、それぞれが別の結果を無視します。

簡単なデモンストレーションを見てみましょう。 今のところ、並列コレクションの実装は無視します。したがって、 parallelExecutionList0〜300整数リストであり、すべての実行を並列化して機能すると仮定します。

val parallelExecutionList = ...

var count = 0

def countTo150(num: Int): Unit = {
  if (num < 150) {
    count += 1
  }
}

parallelExecutionList.foreach(countTo150)

このコードを3回実行した後、カウント結果は変化しました。 最初のカウントは144、2番目は138、3番目は140でした。

別の例を見てみましょう。 parallelExecutionListがランダムな番号を持つListであると仮定しましょう。

val parallelExecutionList = ...

parallelExecutionList.reduce(_%_)

このコードを実行すると、さまざまな結果が得られます。 競合状態は、評価を順次行う必要があるシナリオです。

したがって、並列処理を適用できるかどうか、または適用できるかどうかを判断するには、実行している各タスクを理解することが不可欠です。

3. ScalaParallelコレクション

すでに述べたように、Scalaは並列処理を処理するためのコレクションを実装しています。 メインのシーケンシャルコレクションは、パラレルコレクションと統合されています。 いくつかのクラスを見てみましょう:

  • ParArray
  • ParRange
  • ParVector
  • immutable.ParHashSet
  • immutable.ParHashMap

0〜100の整数のリストを作成しましょう。

val parallelList: ParSeq[Int] = (0 to 100).toList.par

ご覧のとおり、並列コレクションは順次コレクションと簡単に統合でき、parメソッドを使用して並列コレクションに変換できます。 ただし、これらのクラスをシーケンシャルコレクションと同じように利用することは可能です。 たとえば、0から1000までの並列Rangeを作成しましょう。

val parallelVector = ParVector.range(0, 1000)
val otherParallelVector = ParVector.tabulate(1000)(x=>x)

さて、並列計算を実行するには、 filter fold mapなどの従来の方法を使用するだけです。

3.1. マップおよびフィルター機能

map filter 、およびその他の関数は、順次関数と同様の動作をします。

parallelList.map(_ * 2) // ParVector(0, 2, 4, 6, 8, 10, ...) 
parallelList.filter(_ % 2 != 0) // ParVector(1, 3, 5, 7, 9, ...)

ご覧のとおり、実装や結果に違いはなく、タイプのみです。

3.2. 折りたたみ機能

fold 関数は扱いにくい場合があり、関連付けられていない操作を許可します。

parallelList.fold(0)(_ + _) // Int = 5050 

このfoldは、シーケンシャルコレクションの場合と同様に、ゼロから始まるリストのすべての要素を合計していることがわかります。 ただし、関数を減算に変更すると、結合法則のないシナリオに陥ります。

3.3. 並列処理の構成

さらに、Parallel Collectionsを使用すると、タスクを並列化するタイミングと方法を定義する並列化タスク管理をカスタマイズできます。 tasksupport 定義を使用して、構成をセットアップできます。

parallelList.tasksupport = new ForkJoinTaskSupport()

Scalaコレクションアーキテクチャにより、シーケンシャルおよびパラレルコレクションのすべてのコレクションおよびコレクション操作を効率的に利用、維持、および拡張できます。

4. 結論

結論として、Scalaで適用される並列コンピューティングのいくつかの理論的概念を提示しました。 並列処理を行う際に、いくつかのアイデアと制限を検討しました。

要約すると、私たちは主に複雑なタスクと広範なデータで並列処理を使用します。 たとえば、上記のこれらの操作のパフォーマンスを分析すると、これらの操作は非常に単純で最小限であるため、シーケンシャルコレクションがパラレルコレクションよりもパフォーマンスが優れている場合があります。 したがって、これらのアクションを並列化するコストは価値がありません。

いつものように、このチュートリアルで使用されているサンプルコードは、GitHubからで入手できます。