为什么在 Scala 3 中 while 循环填充数组这么慢?

Mik*_*kin 5 arrays optimization scala scala-3

我有2个填充Array[Int]的实现,如下所述。第一个执行时间为 84 毫秒,第二个执行速度慢 100 倍:

Execute 'filling via Array.fill' in 84 ms
Execute 'filling via while' in 8334 ms
Run Code Online (Sandbox Code Playgroud)

为什么第二个变体需要 100 倍的时间?它不是 GC,因为我可以在第二次执行时以相同的时间删除第一次执行。我在 Java 11 上运行它,并使用 Scala 3:

.jdks/adopt-openjdk-11.0.11/bin/java ... Fill
Run Code Online (Sandbox Code Playgroud)

更重要的是,如果您打开Array.fill实现,您将通过 while... 看到实现

Execute 'filling via Array.fill' in 84 ms
Execute 'filling via while' in 8334 ms
Run Code Online (Sandbox Code Playgroud)

PS:我在 Scala 2.12 上重复这个测试:

Execute 'filling via Array.fill' in 118 ms
Execute 'filling via while' in 6 ms
Run Code Online (Sandbox Code Playgroud)

Scala 3 中的问题...

PPS:在这种情况下,for (i <- 0 until n)可以正常速度工作,计时与 Scala 2.12 相同。但在某些情况下,for速度会比 慢 2 到 3 倍while

PPPS:对于那些认为它是随机的或其他什么的人来说,事实并非如此:100 个测试执行 516 秒(今天我的电脑更快),所以平均时间是如此之大。但无论如何,在某些程序中,某些代码块仅执行代码块,因此您不应该对任何性能测试执行平均时间。

更新:我发现当val n位于代码块之外时,执行时间大约慢 1000 倍。但我不明白为什么。

您可以在 Scala 3 编译器的存储库上看到对此问题的评论:https://github.com/lampepfl/dotty/issues/13819

Sar*_*ngh 5

Scala 3 使用这段代码做了一些非常奇怪的事情。

\n

如果我将其重构为不同的结构而不改变逻辑,那么一切都会按预期工作(Array.fill比慢一点while)。

\n
object Fill extends App {\n  def timeMeasure[R](f: => R): (java.lang.Long, R) = {\n    val startTime = System.nanoTime()\n    val r = f\n    val endTime = System.nanoTime()\n    (endTime - startTime, r)\n  }\n\n  def warmup(): Unit =  {\n    println("== warmup start =====================")\n    for (i <- 0 to 10) {\n      measureForN(1000000)\n    }\n    println("== warmup finish =====================")\n  }\n\n  def measureForN(n: Int): Unit = {\n    val t1 = timeMeasure { Array.fill[Int](n)(10) }\n\n    val t2 = timeMeasure({\n      val array = new Array[Int](n)\n      var i = 0\n      while (i < n) {\n        array(i) = 10\n        i += 1\n      }\n      array\n    })\n\n    val t3 = timeMeasure({\n      val array = new Array[Int](n)\n      var i = 0\n      while (i < n) {\n        array(i) = i\n        i += 1\n      }\n      array\n    })\n\n    // just to ensure actual array creations\n    val length = List(t1._2.length, t2._2.length, t3._2.length).min\n\n    println(s"n: ${n}, length: ${length}, fill: ${t1._1 / 1000} \xce\xbcs , while constant: ${t2._1 / 1000} \xce\xbcs, while changing: ${t3._1 / 1000} \xce\xbcs")\n  }\n\n  warmup()\n\n  measureForN(10)\n  measureForN(100)\n  measureForN(1000)\n  measureForN(10000)\n  measureForN(100000)\n  measureForN(1000000)\n  measureForN(10000000)\n  measureForN(100000000)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

输出:

\n
== warmup start =====================\nn: 1000000, length: 1000000, fill: 23533 \xce\xbcs , while constant: 3804 \xce\xbcs, while changing: 3716 \xce\xbcs\nn: 1000000, length: 1000000, fill: 7070 \xce\xbcs , while constant: 1606 \xce\xbcs, while changing: 1783 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3911 \xce\xbcs , while constant: 1497 \xce\xbcs, while changing: 1689 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3821 \xce\xbcs , while constant: 1543 \xce\xbcs, while changing: 1718 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3798 \xce\xbcs , while constant: 1510 \xce\xbcs, while changing: 1662 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3801 \xce\xbcs , while constant: 1524 \xce\xbcs, while changing: 1796 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3896 \xce\xbcs , while constant: 1541 \xce\xbcs, while changing: 1703 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3805 \xce\xbcs , while constant: 1486 \xce\xbcs, while changing: 1687 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3854 \xce\xbcs , while constant: 1606 \xce\xbcs, while changing: 1712 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3836 \xce\xbcs , while constant: 1509 \xce\xbcs, while changing: 1698 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3846 \xce\xbcs , while constant: 1553 \xce\xbcs, while changing: 1672 \xce\xbcs\n== warmup finish =====================\nn: 10, length: 10, fill: 3 \xce\xbcs , while constant: 0 \xce\xbcs, while changing: 0 \xce\xbcs\nn: 100, length: 100, fill: 2 \xce\xbcs , while constant: 3 \xce\xbcs, while changing: 0 \xce\xbcs\nn: 1000, length: 1000, fill: 6 \xce\xbcs , while constant: 1 \xce\xbcs, while changing: 2 \xce\xbcs\nn: 10000, length: 10000, fill: 41 \xce\xbcs , while constant: 19 \xce\xbcs, while changing: 17 \xce\xbcs\nn: 100000, length: 100000, fill: 378 \xce\xbcs , while constant: 156 \xce\xbcs, while changing: 170 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3764 \xce\xbcs , while constant: 1464 \xce\xbcs, while changing: 1676 \xce\xbcs\nn: 10000000, length: 10000000, fill: 36976 \xce\xbcs , while constant: 15687 \xce\xbcs, while changing: 10860 \xce\xbcs\nn: 100000000, length: 100000000, fill: 312242 \xce\xbcs , while constant: 190274 \xce\xbcs, while changing: 221980 \xce\xbcs\n
Run Code Online (Sandbox Code Playgroud)\n

编辑:: 所需的更改就像不直接n使用blocks.

\n
== warmup start =====================\nn: 1000000, length: 1000000, fill: 23533 \xce\xbcs , while constant: 3804 \xce\xbcs, while changing: 3716 \xce\xbcs\nn: 1000000, length: 1000000, fill: 7070 \xce\xbcs , while constant: 1606 \xce\xbcs, while changing: 1783 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3911 \xce\xbcs , while constant: 1497 \xce\xbcs, while changing: 1689 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3821 \xce\xbcs , while constant: 1543 \xce\xbcs, while changing: 1718 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3798 \xce\xbcs , while constant: 1510 \xce\xbcs, while changing: 1662 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3801 \xce\xbcs , while constant: 1524 \xce\xbcs, while changing: 1796 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3896 \xce\xbcs , while constant: 1541 \xce\xbcs, while changing: 1703 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3805 \xce\xbcs , while constant: 1486 \xce\xbcs, while changing: 1687 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3854 \xce\xbcs , while constant: 1606 \xce\xbcs, while changing: 1712 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3836 \xce\xbcs , while constant: 1509 \xce\xbcs, while changing: 1698 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3846 \xce\xbcs , while constant: 1553 \xce\xbcs, while changing: 1672 \xce\xbcs\n== warmup finish =====================\nn: 10, length: 10, fill: 3 \xce\xbcs , while constant: 0 \xce\xbcs, while changing: 0 \xce\xbcs\nn: 100, length: 100, fill: 2 \xce\xbcs , while constant: 3 \xce\xbcs, while changing: 0 \xce\xbcs\nn: 1000, length: 1000, fill: 6 \xce\xbcs , while constant: 1 \xce\xbcs, while changing: 2 \xce\xbcs\nn: 10000, length: 10000, fill: 41 \xce\xbcs , while constant: 19 \xce\xbcs, while changing: 17 \xce\xbcs\nn: 100000, length: 100000, fill: 378 \xce\xbcs , while constant: 156 \xce\xbcs, while changing: 170 \xce\xbcs\nn: 1000000, length: 1000000, fill: 3764 \xce\xbcs , while constant: 1464 \xce\xbcs, while changing: 1676 \xce\xbcs\nn: 10000000, length: 10000000, fill: 36976 \xce\xbcs , while constant: 15687 \xce\xbcs, while changing: 10860 \xce\xbcs\nn: 100000000, length: 100000000, fill: 312242 \xce\xbcs , while constant: 190274 \xce\xbcs, while changing: 221980 \xce\xbcs\n
Run Code Online (Sandbox Code Playgroud)\n

输出 :

\n
== warmup start ==================\nExecute \'filling via Array.fill\' in 26 ms\nExecute \'filling via while\' in 5 ms\n== warmup finish ==================\nExecute \'filling via Array.fill\' in 6 ms\nExecute \'filling via while\' in 1 ms\n
Run Code Online (Sandbox Code Playgroud)\n

编辑 2 :: 正如 Mikhail 所指出的,这是由于 的使用n被编译n()为生成的 Java 代码中的方法的使用而引起的。

\n
object Test3 {\n\n  val n = 1\n\n  val k = n\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

正在编译,

\n
object Fill extends App {\n\n  def timeMeasure[R](name: String)(block: => R): R = {\n    val startTime = System.currentTimeMillis()\n    val r = block\n    val endTime = System.currentTimeMillis()\n    println(s"Execute \'$name\' in ${endTime - startTime} ms")\n    r\n  }\n\n  val n = 1000 * 1000 // * 10\n\n  def measureFill(x: Int): Unit = {\n    val ar1 = timeMeasure("filling via Array.fill") {\n      Array.fill[Int](x)(10)\n    }\n  }\n\n  def measureWhile(x: Int): Unit = {\n    val ar2 = timeMeasure("filling via while") {\n      val array = new Array[Int](x)\n      var i = 0\n      while (i < x) {\n        array(i) = i\n        i += 1\n      }\n      array\n    }\n  }\n\n  println("== warmup ==================")\n  measureFill(n)\n  measureWhile(n)\n  println("== warmup ==================")\n\n  measureFill(n)\n  measureWhile(n)\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但是,Scala 2.13.6 生成的 Java 代码几乎相同(只有 ScalaSignature 部分不同)。

\n
== warmup start ==================\nExecute \'filling via Array.fill\' in 26 ms\nExecute \'filling via while\' in 5 ms\n== warmup finish ==================\nExecute \'filling via Array.fill\' in 6 ms\nExecute \'filling via while\' in 1 ms\n
Run Code Online (Sandbox Code Playgroud)\n

这意味着这是由 Scala 3 中的一些其他错误(导致对n()性能产生如此大的影响)引起的。

\n

  • 实际上我向 Scala 3 编译器添加了问题:https://github.com/lampepfl/dotty/issues/13819 (3认同)