How to profile methods in Scala?
分析scala方法调用的标准方法是什么?
我需要的是一个方法周围的钩子,我可以使用它来启动和停止计时器。
在Java中,我使用方面编程(AspectJ)来定义要被配置的方法和注入字节码来实现相同的方法。
在scala中,是否有一种更自然的方法,在这种方法中,我可以在函数前后定义一组要调用的函数,而不会在过程中丢失任何静态类型?
是否要在不更改要测量其计时的代码的情况下执行此操作?如果您不介意更改代码,那么可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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中自动包装方法调用:
1 2 3 4 5 6 7 |
现在-我们把东西包在里面
1 2 |
好的-我们需要处于电源模式
1 2 3 4 5 6 | 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> ** |
包掉
1 2 3 4 5 6 7 8 9 10 | 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开始更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
您可以利用scala的三个基准库。
由于链接站点上的URL可能会更改,我将粘贴下面的相关内容。
性能测试框架,旨在自动比较性能测试并在简单的构建工具中工作。
scala基准模板-用于创建基于caliper的scala(micro-)基准的SBT模板项目。
度量—捕获JVM—和应用程序级度量。所以你知道发生了什么事
我用的是:
1 2 3 4 5 6 7 8 9 |
1 2 3 4 5 6 7 8 9 10 |
我从Jesper那里获得了解决方案,并在同一代码的多次运行中向它添加了一些聚合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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)) } |
假设您要对两个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 |
希望这有帮助
我使用的技术很容易在代码块中移动。关键是相同的行开始和结束计时器-所以它实际上是一个简单的复制和粘贴。另一个好处是,你可以定义时间对你意味着什么,作为一个字符串,在同一行。
示例用法:
1 2 3 | Timelog("timer name/description") //code to time Timelog("timer name/description") |
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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() } |
赞成的意见:
- 不需要将代码包装为块或在行中进行操作
- 在进行探索时,可以在代码行之间轻松移动计时器的开始和结束
欺骗:
- 对于完全功能的代码来说不那么闪亮
- 显然,如果不"关闭"计时器,这个对象会泄漏映射项,例如,如果您的代码没有进入给定计时器启动的第二次调用。
scalameter是在scala中执行基准测试的好库。
下面是一个简单的例子
1 2 3 4 5 6 7 |
如果在scala工作表中执行上述代码段,则会得到以毫秒为单位的运行时间。
1 | execution_time: org.scalameter.Quantity[Double] = 0.260325 ms |
我喜欢@wrick回答的简单性,但也希望:
探查器处理循环(为了一致性和方便)
更精确的计时(使用纳米时间)
每次迭代的时间(不是所有迭代的总时间)
只返回ns/迭代-不是元组
这是在这里实现的:
1 2 3 4 | def profile[R] (repeat :Int)(code: => R, t: Long = System.nanoTime) = { (1 to repeat).foreach(i => code) (System.nanoTime - t)/repeat } |
为了更加精确,一个简单的修改允许一个jvm热点预热循环(不是定时的)来定时小片段:
1 2 3 4 5 6 |
站在巨人的肩膀上…
一个可靠的第三方库会更理想,但是如果您需要一些基于快速和标准库的东西,下面的变体提供了:
- 重复次数
- 最后一个结果是多次重复获胜
- 多次重复的总时间和平均时间
- 不再需要时间/即时提供程序作为参数
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 } } |
同样值得注意的是,您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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> |
您可以使用
1 2 3 4 5 6 7 |
用途:
1 2 3 | time{ //execute somethings here, like methods, or some codes. } |
奈米时间将向你展示