1

私はscalafuturesを実装しているときにミスを犯しました。少なくとも、間違いを犯したと思いますが、ミスを修正すると、futuresを使用しない場合よりも実行速度が大幅に低下します。誰かが私が何が起こっているのかを理解するのを手伝ってもらえますか?

5,000回実行する必要がある遅いメソッドがあります。それぞれが独立しており、Doubleを返します。次に、5,000個の戻り値の平均と標準偏差を計算する必要があります。

最初にコーディングしたときは、次のようにコーディングしました。

import actors.Futures._
import util.Random
import actors.Future

def one = {
  var results = List[Future[Double]]()
  var expectedResult: List[Double] = Nil
  var i = 0

  while (i < 1000) {
    val f = future {
      Thread.sleep(scala.util.Random.nextInt(5) * 100)
      println("Loop count: " + i)
      Random.nextDouble
    }
    results = results ::: List(f)
    println("Length of results list: " + results.length)

    results.foreach(future => {
      expectedResult = future() :: expectedResult
      i += 1
    })
  }
  // I would return the list of Doubles here to calculate mean and StDev
  println("### Length of final list: " + expectedResult.length)
}

速く走り、期待通りの結果が得られたので、何も考えていませんでした。実行速度を上げるために詳しく調べたところ(使用可能なすべてのCPUリソースを使用していなかった)、ループカウンターが間違った場所にありforeachfuture作成ループ内にあり、その結果、先物を早期にブロックします。またはそう思った。

何が起こっているのかを理解できるかどうかを確認するために、いくつかのprintlnステートメントを貼り付けて、何が起こっているのかについて非常に混乱しました...結果リストの長さが最終リストの長さと一致せず、どちらも一致しませんでしたループカウンター!

私は自分が起こっていると思った(あるべき)ことに基づいてコードを次のように変更しましたが、物事ははるかに遅くなり、printステートメントの出力は最初の方法よりも意味がありませんでした。今回は、最終的なリストの長さは理にかなっていますが、ループカウンターは1000にジャンプするようです。

2番目の方法では、使用可能なすべてのCPUリソースを使用します。これは、私が期待するものに沿ったものですが、同じ結果であると確信している場合は、より時間がかかります。

def two = {
  var results = List[Future[Double]]()
  var expectedResult: List[Double] = Nil
  var i = 0

  while (i < 1000) {
    val f = future {
      Thread.sleep(scala.util.Random.nextInt(5) * 100)
      println("Loop count: " + i)
      Random.nextDouble
    }
    results = f :: results
    i += 1
    println("Length of results list: " + results.length)

  }
  results.foreach(future => {
    expectedResult = future() :: expectedResult
  })
  // I would return the list of Doubles here to calculate mean and StDev
  println("### Length of final list: " + expectedResult.length)
}

ここで明らかな何かが欠けていますか?


編集

これを見ている人にとって...問題は、先物ループ内の最終リスト(expectedResult)に先物の結果を再度追加していたことでした-som-snyttによって指摘されました。

したがって、ループを繰り返すたびに、完了した先物を繰り返し繰り返して、次のようになります。

//First Loop: 
List(1)
//Second Loop:
List(1,2)
//Third Loop:
List(1,2,3,4)
//... and so on

最終的なリストのパターンは次のとおりです。

List(n, n-1, n-2, ..., 4, 3, 2, 1, 3, 2, 1, 2, 1, 1)

リストは5050アイテムの長さで、Double値であるため、リストの先頭だけを見たときにパターンを確認するのは困難でした。

最終的に、ループの数は実際には100だけであり、必要な5000ではありませんでした。

メソッドのバージョン2は、scala2.9に適しています。

4

1 に答える 1

1

ここで明らかな何かが欠けていますか?

いいえ。命令型プログラミングはすべてを非自明にすると言っても過言ではありません。

1つは、結果を繰り返し繰り返し、ぶつかりiます。

前回まで:

Length of results list: 45
Loop count: 990
### Length of final list: 1035

私は最終的なリストを数え、futureを適用すると結果の長さが追加されるので、数学は正しいです:45 + 990 = 1035

完了した先物を適用すると、価値が得られます。待つためだけにブロックするので、将来の価値を何度も取得するパフォーマンスの問題に必ずしも気付くことはありません。

ただし、将来的には、変数iを閉じていることに注意してください。将来が作成されたときのiの値ではなく、 「クロージャによってキャプチャされた」を参照してください。ボーナスの混乱として、同期がないために報告されているように「ループカウント」は信頼できません。

速く走り、期待通りの結果が得られたので、何も考えていませんでした。

その観察には非常に多くの工学的知恵が詰め込まれています。

2.9の他の2つの定式化は次のとおりです。

  def four = (1 to 1000).par map { i =>
    Thread sleep nextInt(5) * 100
    Console println "Loop count: " + i
    nextDouble
  } 

  def three = 
    (1 to 1000) map (i => future {
        Thread sleep nextInt(5) * 100
        Console println "Loop count: " + i
        nextDouble
    }) map (_())

比較のために、2.10の新しいAPIを次に示します。

import scala.concurrent._
import scala.concurrent.duration._
import scala.util._

object Test extends App {
  import ExecutionContext.Implicits.global
  import Random._
  def compute(i: Int) = future {
    Thread.sleep(nextInt(5) * 100)
    val res = nextDouble
    println(s"#$i = $res")
    res
  }
  val f = Future.traverse(1 to 1000)(compute)
  val res = Await result (f, Duration.Inf)
  println(s"Done with ${res.length} results")
}
于 2012-12-16T07:09:49.560 に答える