使用Scala的REPL进行比较性能基准测试是否合理?

maa*_*asg 4 scala performance-testing read-eval-print-loop

Scala的REPL是交互式测试某些代码片段的绝佳场所.最近,我一直在使用REPL进行一些性能比较,以重复执行操作并相对地测量挂钟时间.

这是我最近创建的一个例子,用于帮助回答SO问题[1] [2]:

// Figure out the perfomance difference between direct method invocation and reflection-based method.invoke

def invoke1[T,U](obj:Any, method:Method)(param:T):U = method.invoke(obj,Seq(param.asInstanceOf[java.lang.Object]):_*) match { 
    case x: java.lang.Object if x==null => null.asInstanceOf[U]
    case x => x.asInstanceOf[U]
}

def time[T](b: => T):(T, Long) = {
    val t0 = System.nanoTime()
    val res = b
    val t = System.nanoTime() - t0
    (res,t )
}

class Test {
  def op(l:Long): Long = (2 until math.sqrt(l).toInt).filter(x=>l%x==0).sum
}

val t0 = new Test

val method = classOf[Test].getMethods.find(_.getName=="op").get

def timeDiff = {
  val (timeDirectCall,res) = time { (0 to 1000000).map(x=>t0.op(x)) }
  val (timeInvoke, res2) = time { (0 to 1000000).map(x=>{val res:Long=invoke1(t0,method)(x);res}) }
  (timeInvoke-timeDirectCall).toDouble/timeDirectCall.toDouble
}


//scala> timeDiff
//res60: Double = 2.1428745665357445
//scala> timeDiff
//res61: Double = 2.1604176409796683
Run Code Online (Sandbox Code Playgroud)

在另一个案例中,我一直在生成随机数据点的MM,以比较开源项目的并发模型.REPL非常适合在没有代码编译测试周期的情况下使用不同的配置.

我知道常见的基准测试陷阱,例如JIT优化和热身需求.

我的问题是:

  • 在使用它来执行比较微观基准测试时,是否有任何REPL特定元素需要考虑?

  • 这些测量相对于彼此使用时是否可靠?即他们能否回答这个问题:是否AB

  • 相同代码的预先执行的执行是jit编译器的一个很好的预热吗?

  • 还有其他问题需要注意吗?

[1] Scala反射:如何将对象的方法作为参数传递给另一个方法

[2] https://gist.github.com/maasg/6808879

som*_*ytt 6

这是一个很好的问题.我无法想象为什么有人贬低它.

其中一条评论完全错误的事实表明REPL需要在scala-lang.org的常见问题或教程中占有一席之地.快速搜索后我找不到描述性文件.

答案是肯定的,REPL做到了你所期望的.

这是一个关于为什么问题很有趣的旧页面:REPL感觉很动态,但实际上是静态编译的.它"横跨两个世界",因为关于链接页面的临时评论说明了这一点.

REPL将每一行编译成自己的包装对象.每个这样的对象都从交互式会话的历史中导入符号,这是代码神奇地引用回前一行的方式.所有东西都被编译,因此当它运行时,它可以在JVM上本地运行,可以这么说; 没有额外的解释层.这是REPL的杀手级设计功能.

这就是为什么你的问题的答案是肯定的,你的代码以编译代码的速度运行.调用方法不需要重新编译所有历史记录.

这是另一个旧的链接,显示其他人对时间和微基准测试有同样的问题.

目前存在一个未解决的问题,可以自定义REPL如何包装代码行.微博标记是一个有趣的用例,其中代码可以包含在任意框架中以进行基准测试.那将很快到来.

基准框架应该进行热身.由于提交给REPL的每个表达式都是单独编译的(虽然是由相同的编译器),你会注意到第一次可以调用一个方法,而第二次调用一个方法(通过scalac模数内联).

警告:

使用-Yrepl-class-based或注意不要将计算放在包装对象的静态初始化程序中.

这里有一些样本混淆,这里是同样的问题,隐藏得更少.