为何Kotlin Sequence在小元素量场景下性能反而不如集合?
先说说你遇到的这个有意思的现象:
我曾在Kotlin Playground中对比Kotlin Sequence与Kotlin集合的性能,原本预期Sequence每次都能优于集合,但元素量为10时却并非如此,测试结果如下:Size 10
Lista: 62835 ns
Sekvenca: 7050134 ns==============================
Size 100
Lista: 96139 ns
Sekvenca: 10376 ns==============================
Size 1000
Lista: 702532 ns
Sekvenca: 9689 ns==============================
Size 10000
Lista: 2598859 ns
Sekvenca: 26116 ns==============================
Size 100000
Lista: 11004099 ns
Sekvenca: 45011 ns==============================
Size 1000000
Lista: 623017128 ns
Sekvenca: 55156 ns后续我又添加了元素量为5的测试,这次元素量10的测试结果却变为Sequence占优,这会不会是某种bug?
咱们先拆解第一个问题:为啥Size=10的时候Sequence反而比集合慢这么多?
小数据量下Sequence的“劣势”根源
Kotlin的普通集合(比如你测试里的Lista)是立即求值的——每一步操作(比如map、filter)都会直接生成一个新的中间集合,所有元素一次性处理完。对于只有10个元素的情况,创建中间集合的成本几乎可以忽略,所有操作都是内存里的直接读写,速度极快。
而Sequence是惰性求值的,它不会立即执行操作,而是把每一步操作都包装成一个Sequence对象,只有当你最终调用toList()、count()这类触发终端操作的方法时,才会通过迭代器一步步遍历元素,逐个执行所有操作链里的逻辑。这就带来了额外的开销:创建多个Sequence包装对象、迭代器的调用和切换、每一步操作的延迟执行逻辑。
当数据量极小的时候,这些额外开销的占比会远远超过Sequence“避免中间集合”带来的收益,所以总耗时反而比集合高很多。
为啥加了Size=5的测试后,Size=10的结果就反转了?
这绝对不是bug,而是JVM的即时编译(JIT)预热在起作用。
你第一次运行Size=10的测试时,Sequence相关的代码还处于JVM的解释执行状态——JVM会先把字节码逐行解释成机器码运行,速度很慢。而当你添加了Size=5的测试后,相当于让Sequence的代码多跑了一遍,JVM的热点检测器会发现这段代码被频繁执行,就会把它编译成优化后的机器码(也就是JIT编译)。
等你再跑Size=10的测试时,Sequence的代码已经是编译好的优化版本了,那些包装和迭代的额外开销被大幅降低,这时候Sequence的优势就体现出来了,自然就比集合快了。
额外的小提示
- Sequence的优势只有在数据量足够大或者操作链足够长的时候才会凸显——这时候避免中间集合创建带来的内存和时间收益,会远远超过惰性机制的额外开销。
- Kotlin Playground这种在线环境的JVM预热效果会更明显,因为它的运行资源有限,JIT编译的触发阈值更容易被小范围的测试触发。
内容的提问来源于stack exchange,提问作者ulrich201

