You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中Seq与List填充性能测试异常问题求助

解惑:为什么先调用的Seq/List填充方法总是更慢?

你观察到的这个现象完全是JVM运行时的预热开销导致的,很常见,别担心!我来拆解背后的原因,再给你几个改进测试的方案:

为什么先调用的方法耗时久?

当你第一次调用populateSeq()或者populateList()时,JVM需要完成一堆"初始化工作",这些额外开销都会算在第一次调用的耗时里:

  • 类加载与初始化:JVM需要加载Seq、List以及它们的底层实现类(比如Scala 2里Seq默认就是List,不过依然有类加载的开销),还有字符串常量池的初始化等。
  • JIT编译延迟:JVM的即时编译器(JIT)一开始是用解释模式执行代码,只有当方法被多次调用后,才会把热点代码编译成高效的机器码。第一次调用时完全是解释执行,速度自然慢。
  • 内存分配预热:第一次分配大量对象(比如上百万个字符串)时,JVM可能需要调整堆内存大小、分配新的内存页,甚至触发一次小型GC来整理空间,这些都是额外的耗时。

怎么得到准确的性能对比结果?

1. 手动添加预热步骤

在正式计时前,先调用几次测试方法让JVM完成预热:

object SeqVsList extends App with LazyLogging {
  private val numberOfElements = 1234567
  
  // 预热:先调用几次,让JVM完成类加载和JIT编译
  populateSeq()
  populateList()
  
  // 正式测试
  populateSeq()
  populateList()

  def populateSeq(): Unit = {
    val seqStartTime = System.currentTimeMillis()
    val aSeq = Seq.fill(numberOfElements)("foo")
    logger.info(s"Populating Seq took ${System.currentTimeMillis() - seqStartTime} ms")
  }

  def populateList(): Unit = {
    val listStartTime = System.currentTimeMillis()
    val aList = List.fill(numberOfElements)("bar")
    logger.info(s"Populating List took ${System.currentTimeMillis() - listStartTime} ms")
  }
}

这样你会发现后面的正式测试结果才是真实的性能对比。

2. 使用专业的基准测试框架(推荐)

手动计时很难处理所有JVM的细节,最靠谱的方式是用JMH(Java Microbenchmark Harness),它专门为JVM上的微基准测试设计,会自动处理预热、多次迭代、统计结果等问题。

简单的JMH示例大概是这样:

import org.openjdk.jmh.annotations._
import java.util.concurrent.TimeUnit

@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
class SeqVsListBenchmark {
  private val numberOfElements = 1234567

  @Benchmark
  def populateSeq(): Seq[String] = {
    Seq.fill(numberOfElements)("foo")
  }

  @Benchmark
  def populateList(): List[String] = {
    List.fill(numberOfElements)("bar")
  }
}

运行这个基准测试,你会得到非常准确的平均耗时对比。

3. 补充:Scala中Seq和List的小知识

顺便提一句:在Scala 2中,Seq的默认实现就是List,所以Seq.fill底层其实和List.fill是一样的逻辑;如果是Scala 3,Seq的默认实现变成了ArraySeq,这时候两者的性能才会有明显差异。所以你的测试在Scala 2里可能结果差不多,Scala 3里会看到区别。

内容的提问来源于stack exchange,提问作者mvee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:34:44