当前位置: 首页 > 知识库问答 >
问题:

为什么在Scala 3中循环如此缓慢时填充数组?

葛成济
2023-03-14

我有两个填充数组[Int]的实现,如下所述。第一个以84毫秒的速度执行,第二个以100倍的速度执行:

Execute 'filling via Array.fill' in 84 ms
Execute 'filling via while' in 8334 ms

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

.jdks/adopt-openjdk-11.0.11/bin/java ... Fill

更重要的是,如果你打开数组。填充implementation,您将通过while。。。

object Fill extends App {
  def timeMeasure[R](name: String)(block: => R): R = {
    val startTime = System.currentTimeMillis()
    val r = block
    val endTime = System.currentTimeMillis()
    println(s"Execute '$name' in ${endTime - startTime} ms")
    r
  }

  val n = 1000 * 1000 // * 10
  var ar = timeMeasure("filling via Array.fill") {
    Array.fill[Int](n)(10)
  }
  ar = timeMeasure("filling via while") {
    val array = new Array[Int](n)
    var i = 0
    while (i < n) {
      array(i) = i
      i += 1
    }
    array
  }
}

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

Execute 'filling via Array.fill' in 118 ms
Execute 'filling via while' in 6 ms

所以Scala 3的问题...

PPS:在这种情况下,for(i)

购买力平价:对于那些认为它是随机的或是其他什么的人来说,它不是:100个测试执行516秒(今天我的电脑更快),所以平均时间太长了。但无论如何,在一些程序中,一些代码块只执行一个,所以您不应该执行任何性能测试的平均时间。

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

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


共有1个答案

汪弘光
2023-03-14

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

如果我在不改变逻辑的情况下将其重构为不同的结构,那么一切都会按预期工作(Array.fillwhile慢一点)。

object Fill extends App {
  def timeMeasure[R](f: => R): (java.lang.Long, R) = {
    val startTime = System.nanoTime()
    val r = f
    val endTime = System.nanoTime()
    (endTime - startTime, r)
  }

  def warmup(): Unit =  {
    println("== warmup start =====================")
    for (i <- 0 to 10) {
      measureForN(1000000)
    }
    println("== warmup finish =====================")
  }

  def measureForN(n: Int): Unit = {
    val t1 = timeMeasure { Array.fill[Int](n)(10) }

    val t2 = timeMeasure({
      val array = new Array[Int](n)
      var i = 0
      while (i < n) {
        array(i) = 10
        i += 1
      }
      array
    })

    val t3 = timeMeasure({
      val array = new Array[Int](n)
      var i = 0
      while (i < n) {
        array(i) = i
        i += 1
      }
      array
    })

    // just to ensure actual array creations
    val length = List(t1._2.length, t2._2.length, t3._2.length).min

    println(s"n: ${n}, length: ${length}, fill: ${t1._1 / 1000} μs , while constant: ${t2._1 / 1000} μs, while changing: ${t3._1 / 1000} μs")
  }

  warmup()

  measureForN(10)
  measureForN(100)
  measureForN(1000)
  measureForN(10000)
  measureForN(100000)
  measureForN(1000000)
  measureForN(10000000)
  measureForN(100000000)
}

输出:

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

编辑::所需的更改非常简单,只要不直接使用块中的n

object Fill extends App {

  def timeMeasure[R](name: String)(block: => R): R = {
    val startTime = System.currentTimeMillis()
    val r = block
    val endTime = System.currentTimeMillis()
    println(s"Execute '$name' in ${endTime - startTime} ms")
    r
  }

  val n = 1000 * 1000 // * 10

  def measureFill(x: Int): Unit = {
    val ar1 = timeMeasure("filling via Array.fill") {
      Array.fill[Int](x)(10)
    }
  }

  def measureWhile(x: Int): Unit = {
    val ar2 = timeMeasure("filling via while") {
      val array = new Array[Int](x)
      var i = 0
      while (i < x) {
        array(i) = i
        i += 1
      }
      array
    }
  }

  println("== warmup ==================")
  measureFill(n)
  measureWhile(n)
  println("== warmup ==================")

  measureFill(n)
  measureWhile(n)

}

输出:

== warmup start ==================
Execute 'filling via Array.fill' in 26 ms
Execute 'filling via while' in 5 ms
== warmup finish ==================
Execute 'filling via Array.fill' in 6 ms
Execute 'filling via while' in 1 ms

编辑2:: 正如Mikhail所指出的,这是因为n的用法与生成Java代码中方法n()的用法相同。

object Test3 {

  val n = 1

  val k = n

}

正在被编译成,

//decompiled from Test3.class
public final class Test3 {
   public static int k() {
      return Test3$.MODULE$.k();
   }

   public static int n() {
      return Test3$.MODULE$.n();
   }
}

        //decompiled from Test3$.class
import java.io.Serializable;
import scala.runtime.ModuleSerializationProxy;

public final class Test3$ implements Serializable {
   private static final int n = 1;
   private static final int k;
   public static final Test3$ MODULE$ = new Test3$();

   private Test3$() {
   }

   static {
      k = MODULE$.n();
   }

   private Object writeReplace() {
      return new ModuleSerializationProxy(Test3$.class);
   }

   public int n() {
      return n;
   }

   public int k() {
      return k;
   }
}

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

//decompiled from Test3.class
import scala.reflect.ScalaSignature;

@ScalaSignature(
   bytes = "\u0006\u0005\r:Qa\u0002\u0005\t\u0002=1Q!\u0005\u0005\t\u0002IAQ!G\u0001\u0005\u0002iAqaG\u0001C\u0002\u0013\u0005A\u0004\u0003\u0004!\u0003\u0001\u0006I!\b\u0005\bC\u0005\u0011\r\u0011\"\u0001\u001d\u0011\u0019\u0011\u0013\u0001)A\u0005;\u0005)A+Z:ug)\u0011\u0011BC\u0001\ta\u0016\u0014X.\u00192be*\u00111\u0002D\u0001\u0005g\u0016\u0014\u0018NC\u0001\u000e\u0003\tiWm\u0001\u0001\u0011\u0005A\tQ\"\u0001\u0005\u0003\u000bQ+7\u000f^\u001a\u0014\u0005\u0005\u0019\u0002C\u0001\u000b\u0018\u001b\u0005)\"\"\u0001\f\u0002\u000bM\u001c\u0017\r\\1\n\u0005a)\"AB!osJ+g-\u0001\u0004=S:LGO\u0010\u000b\u0002\u001f\u0005\ta.F\u0001\u001e!\t!b$\u0003\u0002 +\t\u0019\u0011J\u001c;\u0002\u00059\u0004\u0013!A6\u0002\u0005-\u0004\u0003"
)
public final class Test3 {
   public static int k() {
      return Test3$.MODULE$.k();
   }

   public static int n() {
      return Test3$.MODULE$.n();
   }
}

        //decompiled from Test3$.class
public final class Test3$ {
   public static final Test3$ MODULE$ = new Test3$();
   private static final int n = 1;
   private static final int k;

   static {
      k = MODULE$.n();
   }

   public int n() {
      return n;
   }

   public int k() {
      return k;
   }

   private Test3$() {
   }
}

这意味着这是由Scala 3中的其他一些错误(导致这个n()产生这样的性能影响)造成的。

 类似资料:
  • 这与 R- 查看具有任何 NA 的所有列名称有关 我比较了data.frame和data.table版本,发现data.table慢了10倍。这与大多数使用data.table的代码相反,后者确实比data.frame版本快得多。 预先设置: 可能是什么原因?

  • 编辑:为什么在局部变量上这么快?(~16秒进行相同的迭代,但对函数内部的局部变量进行迭代)

  • 为什么我的parkList变量在.each函数外是空的,而我在循环外声明了它并在循环期间填充了它的值...并且循环成功执行?

  • 为了好玩,我决定用红宝石编码伊拉托西筛子。只是为了好玩,因为我知道有一个库函数。而且,我认为它会很快。但我发现它并不是,至少在我的ruby 1.9.3中,我的上网本速度快了好几倍,甚至在c中也没有。为什么会这样呢。 库实现: 我在红宝石: 图书馆非常慢。

  • 问题内容: 我们正在做一些和实现Python编写的。其他人选择了Java。我们的执行时间非常不同。我使用cProfile查看我在哪里出错,但实际上一切都很好。是的,我也使用。但是我想问一个简单的问题。 此摘要在我的计算机上耗时31.40s。 此代码的Java版本在同一台计算机上花费1秒或更短的时间。我想类型检查是此代码的主要问题。但是我应该为我的项目做很多这样的操作,我认为9999 *9999的数

  • 对于特定的任务,我需要在可变数组中进行大量快速、单独的写操作。为了检查性能,我使用了以下测试: