如何在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可能会发生变化,因此我正在粘贴以下相关内容。
-
SPerformance – 性能testing框架,旨在自动比较性能testing和Simple Build Tool内部的工作。
-
scala-benchmarking-template – 用于创build基于Caliper的Scala(微)基准的SBT模板项目。
-
度量标准 – 捕获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_new
和counter_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来代替它。