Scala循环性能问题,为了性能,你愿意牺牲代码的可维护性么?

  最近我在学习我们产品的代码,看到了类似以下的一段代码:

  x.set(1)

  x.set(2)

  x.set(3)

  x.set(4)

  x.set(5)

  我当时很是疑惑,为什么不用循环呢?于是就报了一个Issue,心想这样写可能有它的道理,但是需要澄清一下。

  另一个问题,就是我发现代码里对循环的使用,各有不同的方式,有人写array.foreach(f=>_),有人用使用index的for loop,个人觉得使用foreach的代码比较简洁,于是我也报了Issue,看看是不是应该使用简洁的方式来写循环。举例:

  for loop

  var index=0

  var arr=Array[String]

  var length=arr.length

  for ( index

  do()

  }

  for each

  var index=0

  var arr=Array[String]

  var length=arr.length

  for ( index

  do()

  }

  明显foreach的版本要省不少代码。

  后来和我们的工程师沟通了一下,原来我们是为了性能优化了代码,因为for loop比foreach的性能好,所以我们采用稍微繁琐的for loop。至于某些代码中的foreach是因为遗留的还没有来得及改动。

  Scala的循环就行性能如何呢?我还是测试一下再说吧。

  先看看不同的循环用法,我这里测试了四种,分别是 while loop,for loop,使用range的foreach, 和使用函数的foreach

  测试代码如下:

  -

  package profiling

  object Loop {

  def whileLoop(arr:Array[Int]): Unit={

  var idx=0

  var n=arr.length

  val tStart=System.currentTimeMillis()

  while (idx < n) {

  arr(idx)=1

  idx +=1

  }

  val tEnd=System.currentTimeMillis()

  println("while loop took " + (tEnd - tStart) + "ms")

  }

  def forLoop(arr:Array[Int]): Unit={

  var idx=0

  var n=arr.length

  val tStart=System.currentTimeMillis()

  for(idx

  arr(idx)=1

  }

  val tEnd=System.currentTimeMillis()

  println("for loop took " + (tEnd - tStart) + "ms")

  }

  def foreachLoop(arr:Array[Int]): Unit={

  var n=arr.length

  val tStart=System.currentTimeMillis()

  (0 until n).foreach{idx=> arr(idx)=1}

  val tEnd=System.currentTimeMillis()

  println("foreach range took " + (tEnd - tStart) + "ms")

  }

  def foreachFuncLoop(arr:Array[Int]): Unit={

  val tStart=System.currentTimeMillis()

  arr.foreach{ idx=> arr(idx)=1}

  val tEnd=System.currentTimeMillis()

  println("foreach function took " + (tEnd - tStart) + "ms")

  }

  def profileRun(n: Int) {

  val arr=new Array[Int](n)

  whileLoop(arr)

  foreachLoop(arr)

  forLoop(arr)

  foreachFuncLoop(arr)

  }

  def main(args:Array[String]) {

  profileRun(args(0).toInt)

  }

  }

  我的环境是scala 2.13.1 , 调用500000000次的结果是:

  -

  Bash 代码

  while loop took 344ms

  foreach range took 484ms

  for loop took 422ms

  foreach function took 719ms

  可以看出,while loop是最快的,一般形式的foreach最慢,差不多是while loop的一倍。但是如果使用range的话,foreach循环也不算太慢。

  那么为什么foreach会慢呢? 主要是foreach的函数调用带来了额外的开销。我们上面看到的数据其实是编译器已经优化后的数字,如果我们把java的hotspot编译选项关闭,(-Xint)再看看性能。

  while loop took 8548ms

  foreach range took 39392ms

  for loop took 40799ms

  foreach function took 103489ms

  如果关闭JIT,foreach的性能要远远差于其他几个选项。

  对于循环的性能,我们可以得出这样的结论:

  在正常打开JIT的情况下,foreach的性能大概比其他几个选项慢一倍,其他几个选项性能接近在关闭JIT优化的情况下。foreach的性能要远低于其他选项 (生产环境一般不考虑)

  那么对于开头讲的不用循环,直接重复代码呢?我们也测试了一下:

  package profiling

  object Loop2Repeat {

  def whileLoop(): Unit={

  var idx=0

  var n=5

  var x=0

  while (idx < n) {

  x=idx

  idx +=1

  }

  }

  def repeatLoop(): Unit={

  var x=0

  x=1

  x=2

  x=3

  x=4

  x=5

  }

  def test( f:()=>Unit, num: Int, name: String): Unit={

  val tStart=System.currentTimeMillis()

  ( 0 until num).foreach{ _=> f}

  val tEnd=System.currentTimeMillis()

  println(name + " took " + (tEnd - tStart) + "ms")

  }

  def main(args:Array[String]) {

  test(whileLoop, 50000000, "whileLoop")

  test(repeatLoop, 50000000, "repeatLoop")

  }

  }

  经过50000000次循环,数据如下:

  whileLoop took 281ms

  repeatLoop took 47ms

  确实,因为循环控制的逻辑带来的额外开销,比简单的重复代码性能下降了不少。

  为了性能,你愿意牺牲代码的可维护性么? 

posted @   linjingyg  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示