网站首页 > 基础教程 正文
最近我在学习我们产品的代码,看到了类似以下的一段代码:
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 <- 0 to length ) {
do()
}
for each
var index = 0
var arr = Array[String]
var length = arr.length
for ( index <- 0 to length ) {
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 <- 0 until n) {
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
确实,因为循环控制的逻辑带来的额外开销,比简单的重复代码性能下降了不少。
好了,数据我们都有了,问题来了,为了性能考虑,你愿意牺牲多少代码的简洁性和可读性呢?有兴趣的读者可以参加本文中的投票,给出你的意见。
我的观点:
- 性能很重要,但是为了性能而牺牲代码的可读性,可维护性,我觉得是值得考虑的,除非是项目非常关键的部件,我会倾向保留代码的可维护性。
- 我们的项目是Java/Scala混编,本来用Scala就是为了它的一些先进的语法特性,主要是代码的易读易写。为了性能优化,我们把Scala的代码写的和Java一样或者还不如Java易读,是否有悖我们采用Scala的初衷呢?
猜你喜欢
- 2024-10-12 Scala初学者入门指南!涵盖20多个基本技巧
- 2024-10-12 使用Apache Kafka时的7个错误 apache kafka实战pdf
- 2024-10-12 scala中为什么不建议用return scala for until
- 2024-10-12 scala——泛型方法、类、特质的使用,泛型边界、协变逆变非变
- 2024-10-12 程序员构建总是出问题,怎么办? 程序员构建总是出问题,怎么办呢
- 2024-10-12 scala——列表、元祖、列表相关知识
- 2024-10-12 scala语言基础图解-第一阶段(变量-条件-循环-方法-函数-集合)
- 2024-10-12 Scala入门视频已更新至88讲,后续还有大约20讲左右的视频就结束
- 2024-10-12 Scala快速入门 - 环境安装篇 scala安装步骤
- 2024-10-12 Scala 安装及环境配置 scala安装及环境配置
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)