Kotlin中IntArray的lastIndex与size-1性能差异疑问:为何lastIndex执行更慢?
嘿,这个问题确实挺让人困惑的——明明翻Kotlin源码能看到lastIndex的实现就是size - 1,但实际跑起来却比直接写size - 1慢,我来帮你梳理下可能的原因:
方法调用的额外开销
虽然lastIndex的逻辑和size - 1完全一致,但它是一个扩展属性,在JVM层面会被编译成静态方法调用(类似IntArrays.getLastIndex(nums)的形式)。哪怕这个方法只有一行代码,初次执行时也会有方法入栈出栈的开销;而直接写nums.size - 1时,size对应JVM原生数组的length字段,是直接访问内存的操作,减法也是简单的算术运算,JVM能直接优化,没有额外的方法调用成本。JIT编译的预热差异
JVM的即时编译器(JIT)需要一定的执行次数才会把热点代码内联、优化。直接写size - 1的逻辑更直白,JIT能更快识别并优化;而lastIndex的方法调用,在初次测试的短时间内可能还没被JVM内联,所以会体现出性能差异。如果你把测试代码重复跑几千次,等JVM充分预热后,两者的性能差距会大幅缩小甚至消失——因为JIT会把lastIndex的getter方法直接内联成size - 1的逻辑。单次测试的误差干扰
你当前的测试只跑了一次循环,单次执行的时间太短,System.nanoTime()的测量结果很容易受到JVM线程调度、垃圾回收等因素的干扰,误差很大。建议把整个测试逻辑封装成函数,重复执行上万次,然后计算平均耗时,这样得到的结果才更可信。
比如可以修改测试代码,像这样:
fun main() { val tar = 2 val repeatCount = 100000 // 预热JVM testSizeMinusOne(intArrayOf(0,1,2), tar) testLastIndex(intArrayOf(0,1,2), tar) // 正式测试 var totalSizeMinusOne = 0L repeat(repeatCount) { totalSizeMinusOne += testSizeMinusOne(intArrayOf(0, 1, 2, 2, 3, 0, 4, 2), tar) } println("size-1 平均耗时:${totalSizeMinusOne / repeatCount} ns") var totalLastIndex = 0L repeat(repeatCount) { totalLastIndex += testLastIndex(intArrayOf(0, 1, 2, 2, 3, 0, 4, 2), tar) } println("lastIndex 平均耗时:${totalLastIndex / repeatCount} ns") } fun testSizeMinusOne(nums: IntArray, tar: Int): Long { var count = 0 var i = 0 val begin = System.nanoTime() val n = nums.size - 1 while (i <= n) { if (nums[i] != tar) { nums[count++] = nums[i] } i++ } return System.nanoTime() - begin } fun testLastIndex(nums: IntArray, tar: Int): Long { var count = 0 var i = 0 val begin = System.nanoTime() val n = nums.lastIndex while (i <= n) { if (nums[i] != tar) { nums[count++] = nums[i] } i++ } return System.nanoTime() - begin }
跑这个版本的测试,你会发现预热后两者的耗时差距几乎可以忽略。
最后总结一下:这种差异本质上是JVM层面方法调用与直接字段访问的临时性能差,当JVM充分优化后,两者的表现会趋于一致。日常开发中不用太纠结这个——除非你在写极致性能敏感的代码,否则lastIndex的可读性优势更值得优先考虑。
备注:内容来源于stack exchange,提问作者TblPK

