如何在Scala中剖析方法?

什么是分析Scala方法调用的标准方法?

我需要的是一个方法钩,我可以用它来启动和停止计时器。

在Java中,我使用方面编程aspectJ来定义要分析的方法,并注入字节码以实现相同。

在Scala中有没有更自然的方法,我可以在函数前后定义一堆函数,而不会在程序中丢失任何静态types?

你想在不改变你想要测量时间的代码的情况下做到这一点? 如果你不介意改变代码,那么你可以做这样的事情:

def time[R](block: => R): R = { val t0 = System.nanoTime() val result = block // call-by-name val t1 = System.nanoTime() println("Elapsed time: " + (t1 - t0) + "ns") result } // Now wrap your method calls, for example change this... val result = 1 to 1000 sum // ... into this val result = time { 1 to 1000 sum } 

除了Jesper的回答,您还可以在REPL中自动包装方法调用:

 scala> def time[R](block: => R): R = { | val t0 = System.nanoTime() | val result = block | println("Elapsed time: " + (System.nanoTime - t0) + "ns") | result | } time: [R](block: => R)R 

现在,让我们来包装任何东西

 scala> :wrap time wrap: no such command. Type :help for help. 

好的 – 我们需要进入供电模式

 scala> :power ** Power User mode enabled - BEEP BOOP SPIZ ** ** :phase has been set to 'typer'. ** ** scala.tools.nsc._ has been imported ** ** global._ and definitions._ also imported ** ** Try :help, vals.<tab>, power.<tab> ** 

裹着

 scala> :wrap time Set wrapper to 'time' scala> BigDecimal("1.456") Elapsed time: 950874ns Elapsed time: 870589ns Elapsed time: 902654ns Elapsed time: 898372ns Elapsed time: 1690250ns res0: scala.math.BigDecimal = 1.456 

我不知道为什么打印出来的东西5次

更新至2.12.2:

 scala> :pa // Entering paste mode (ctrl-D to finish) package wrappers { object wrap { def apply[A](a: => A): A = { println("running...") ; a } }} // Exiting paste mode, now interpreting. scala> $intp.setExecutionWrapper("wrappers.wrap") scala> 42 running... res2: Int = 42 

有三个基准库Scala ,你可以利用。

由于链接网站上的url可能会发生变化,因此我正在粘贴以下相关内容。

  1. SPerformance – 性能testing框架,旨在自动比较性能testing和Simple Build Tool内部的工作。

  2. scala-benchmarking-template – 用于创build基于Caliper的Scala(微)基准的SBT模板项目。

  3. 度量标准 – 捕获JVM和应用程序级度量。 所以你知道发生了什么事

这是我用的:

 import System.nanoTime def profile[R](code: => R, t: Long = nanoTime) = (code, nanoTime - t) // usage: val (result, time) = profile { /* block of code to be profiled*/ } val (result2, time2) = profile methodToBeProfiled(foo) 

testing.Benchmark可能是有用的。

 scala> def testMethod {Thread.sleep(100)} testMethod: Unit scala> object Test extends testing.Benchmark { | def run = testMethod | } defined module Test scala> Test.main(Array("5")) $line16.$read$$iw$$iw$Test$ 100 100 100 100 100 

我使用了一种易于在代码块中移动的技术。 关键是同一条确切的线开始和结束计时器 – 所以它是一个简单的复制和粘贴。 另一件好事是你可以定义什么时间对你来说意味着一个string,都在同一行。

用法示例:

 Timelog("timer name/description") //code to time Timelog("timer name/description") 

代码:

 object Timelog { val timers = scala.collection.mutable.Map.empty[String, Long] // // Usage: call once to start the timer, and once to stop it, using the same timer name parameter // def timer(timerName:String) = { if (timers contains timerName) { val output = s"$timerName took ${(System.nanoTime() - timers(timerName)) / 1000 / 1000} milliseconds" println(output) // or log, or send off to some performance db for analytics } else timers(timerName) = System.nanoTime() } 

优点:

  • 无需将代码封装为块或在行内进行操作
  • 在探索时可以很容易地将定时器的开始和结束在代码行之间移动

缺点:

  • 对于完全function的代码来说,shiny点更less
  • 显然这个对象会泄漏map条目,如果你不closures定时器,例如,如果你的代码没有得到给定定时器启动的第二次调用。

我喜欢@ wrick的答案的简单,但也想:

  • 分析器处理循环(为了一致性和方便性)

  • 更准确的时间(使用nanoTime)

  • 每次迭代的时间(不是所有迭代的总时间)

  • 只是返回ns /迭代 – 不是一个元组

这是在这里实现的:

 def profile[R] (repeat :Int)(code: => R, t: Long = System.nanoTime) = { (1 to repeat).foreach(i => code) (System.nanoTime - t)/repeat } 

为了更加准确,一个简单的修改允许一个JVM热点热身循环(不定时)来定时小片段:

 def profile[R] (repeat :Int)(code: => R) = { (1 to 10000).foreach(i => code) // warmup val start = System.nanoTime (1 to repeat).foreach(i => code) (System.nanoTime - start)/repeat } 

我从Jesper那里获得了解决scheme,并在同一代码的多次运行中添加了一些聚合

 def time[R](block: => R) = { def print_result(s: String, ns: Long) = { val formatter = java.text.NumberFormat.getIntegerInstance println("%-16s".format(s) + formatter.format(ns) + " ns") } var t0 = System.nanoTime() var result = block // call-by-name var t1 = System.nanoTime() print_result("First Run", (t1 - t0)) var lst = for (i <- 1 to 10) yield { t0 = System.nanoTime() result = block // call-by-name t1 = System.nanoTime() print_result("Run #" + i, (t1 - t0)) (t1 - t0).toLong } print_result("Max", lst.max) print_result("Min", lst.min) print_result("Avg", (lst.sum / lst.length)) } 

假设你想要两个函数counter_newcounter_old ,下面是用法:

 scala> time {counter_new(lst)} First Run 2,963,261,456 ns Run #1 1,486,928,576 ns Run #2 1,321,499,030 ns Run #3 1,461,277,950 ns Run #4 1,299,298,316 ns Run #5 1,459,163,587 ns Run #6 1,318,305,378 ns Run #7 1,473,063,405 ns Run #8 1,482,330,042 ns Run #9 1,318,320,459 ns Run #10 1,453,722,468 ns Max 1,486,928,576 ns Min 1,299,298,316 ns Avg 1,407,390,921 ns scala> time {counter_old(lst)} First Run 444,795,051 ns Run #1 1,455,528,106 ns Run #2 586,305,699 ns Run #3 2,085,802,554 ns Run #4 579,028,408 ns Run #5 582,701,806 ns Run #6 403,933,518 ns Run #7 562,429,973 ns Run #8 572,927,876 ns Run #9 570,280,691 ns Run #10 580,869,246 ns Max 2,085,802,554 ns Min 403,933,518 ns Avg 797,980,787 ns 

希望这是有帮助的

当站在巨人的肩膀上时…

一个可靠的第三方库会更理想,但是如果你需要一些快速和基于std-library的库,下面的变体提供了:

  • 重复
  • 最后的结果赢得多次重复
  • 总时间和多次重复的平均时间
  • 不需要时间/即时提供者作为参数

 import scala.concurrent.duration._ import scala.language.{postfixOps, implicitConversions} package object profile { def profile[R](code: => R): R = profileR(1)(code) def profileR[R](repeat: Int)(code: => R): R = { require(repeat > 0, "Profile: at least 1 repetition required") val start = Deadline.now val result = (1 until repeat).foldLeft(code) { (_: R, _: Int) => code } val end = Deadline.now val elapsed = ((end - start) / repeat) if (repeat > 1) { println(s"Elapsed time: $elapsed averaged over $repeat repetitions; Total elapsed time") val totalElapsed = (end - start) println(s"Total elapsed time: $totalElapsed") } else println(s"Elapsed time: $elapsed") result } } 

另外值得一提的是,你可以使用Duration.toCoarsest方法将时间单位转换为最大时间单位,但我不确定这是多么友好这是在运行之间有微小的时间差异,例如

 Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60). Type in expressions to have them evaluated. Type :help for more information. scala> import scala.concurrent.duration._ import scala.concurrent.duration._ scala> import scala.language.{postfixOps, implicitConversions} import scala.language.{postfixOps, implicitConversions} scala> 1000.millis res0: scala.concurrent.duration.FiniteDuration = 1000 milliseconds scala> 1000.millis.toCoarsest res1: scala.concurrent.duration.Duration = 1 second scala> 1001.millis.toCoarsest res2: scala.concurrent.duration.Duration = 1001 milliseconds scala> 

ScalaMeter是在Scala中执行基准testing的一个不错的库

下面是一个简单的例子

 import org.scalameter._ def sumSegment(i: Long, j: Long): Long = (i to j) sum val (a, b) = (1, 1000000000) val execution_time = measure { sumSegment(a, b) } 

如果你在Scala Worksheet中执行上面的代码片段,你会得到毫秒的运行时间

 execution_time: org.scalameter.Quantity[Double] = 0.260325 ms 

你可以使用System.currentTimeMillis

 def time[R](block: => R): R = { val t0 = System.currentTimeMillis() val result = block // call-by-name val t1 = System.currentTimeMillis() println("Elapsed time: " + (t1 - t0) + "ms") result } 

用法:

 time{ //execute somethings here, like methods, or some codes. } 

nanoTime会显示你ns ,所以很难看到。 所以我build议你可以使用currentTimeMillis来代替它。