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

时间:2013-10-07 21:01:52

标签: 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

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

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

我的问题是:

  • 使用时是否需要考虑REPL特定元素 它执行宏观基准比较微观?

  • 这些测量相对于彼此使用时是否可靠?也就是说他们可以回答这个问题:AB更快吗?

  • 相同代码的预删除执行是jit的良好预热 编译器?

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

[1] Scala reflection: How to pass an object's method as parameter to another method

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

1 个答案:

答案 0 :(得分:6)

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

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

答案是肯定的,REPL符合您的期望。

Here is an old page关于为什么这个问题很有趣:REPL感觉很动态,但实际上是静态编译的。它“横跨两个世界”,因为链接页面的临时评论表明了这一点。

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

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

Here's another old link显示其他人对时间和微基准测试有同样的疑问。

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

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

警告:

使用-Yrepl-class-based或小心不要将计算放在包装对象的静态初始值设定项中。

Here is some sample confusionhere is the same question,隐藏得更少。