分析Scala方法调用的标准方法是什么?
我需要的是一个方法的钩子,我可以使用它来启动和停止计时器。
在Java中,我使用方面编程aspectJ来定义要分析的方法并注入字节码以实现相同的目的。
在Scala中是否有更自然的方式,我可以在函数前后定义一组函数,而不会在过程中丢失任何静态类型?
答案 0 :(得分:200)
您是否希望在不更改要测量时序的代码的情况下执行此操作?如果您不介意更改代码,那么您可以执行以下操作:
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 }
答案 1 :(得分:33)
除了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
答案 2 :(得分:22)
您可以使用three benchmarking libraries for Scala。
由于链接网站上的网址可能会发生变化,因此我会粘贴以下相关内容。
SPerformance - 性能测试框架旨在自动比较性能测试和在Simple Build Tool中工作。
scala-benchmarking-template - 基于Caliper创建Scala(微)基准测试的SBT模板项目。
Metrics - 捕获JVM和应用程序级指标。所以你知道发生了什么
答案 3 :(得分:21)
我使用的是:
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)
答案 4 :(得分:6)
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
答案 5 :(得分:5)
我从Jesper那里获得了解决方案,并在同一代码的多次运行中添加了一些聚合
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
希望这很有帮助
答案 6 :(得分:4)
我使用的技术很容易在代码块中移动。关键是同一条线开始和结束计时器 - 所以它实际上是一个简单的复制和粘贴。另一件好事是你可以在同一行中定义时间对字符串的意义。
使用示例:
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()
}
优点:
缺点:
答案 7 :(得分:4)
推荐的对Scala代码进行基准测试的方法是通过sbt-jmh
“不要相信任何人,把一切都放在长凳上。” -JMH(Java的sbt插件 微基准测试线束)
例如,许多主要的Scala项目都采用了这种方法,
基于System.nanoTime
的简单包装计时器是基准测试的not a reliable method:
System.nanoTime
现在和String.intern
一样糟糕:您可以使用它, 但要明智地使用它。延迟,粒度和可伸缩性影响 计时器引入的时间可能会并会影响您的测量 没有适当的严谨。这是为什么System.nanoTime
应该通过基准测试从用户中抽象出来 框架
此外,诸如JIT warmup,垃圾回收,系统范围内的事件等注意事项可能introduce unpredictability成为衡量指标:
需要减轻许多影响,包括预热,无效代码 消除,分叉等。幸运的是,JMH已经照顾了许多人 东西,并且具有Java和Scala的绑定。
基于Travis Brown's answer,这是example,说明如何为Scala设置JMH基准
project/plugins.sbt
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7")
build.sbt
中启用jmh插件
enablePlugins(JmhPlugin)
添加到src/main/scala/bench/VectorAppendVsListPreppendAndReverse.scala
package bench
import org.openjdk.jmh.annotations._
@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.AverageTime))
class VectorAppendVsListPreppendAndReverse {
val size = 1_000_000
val input = 1 to size
@Benchmark def vectorAppend: Vector[Int] =
input.foldLeft(Vector.empty[Int])({ case (acc, next) => acc.appended(next)})
@Benchmark def listPrependAndReverse: List[Int] =
input.foldLeft(List.empty[Int])({ case (acc, next) => acc.prepended(next)}).reverse
}
sbt "jmh:run -i 10 -wi 10 -f 2 -t 1 bench.VectorAppendVsListPreppendAndReverse"
结果是
Benchmark Mode Cnt Score Error Units
VectorAppendVsListPreppendAndReverse.listPrependAndReverse avgt 20 0.024 ± 0.001 s/op
VectorAppendVsListPreppendAndReverse.vectorAppend avgt 20 0.130 ± 0.003 s/op
似乎表明在List
之前添加了前缀,然后在最后将其反转,这比继续添加到Vector
上要快几个数量级。
答案 8 :(得分:3)
我喜欢@wickx答案的简单性,但也想要:
探查器处理循环(为了一致性和方便性)
更精确的计时(使用nanoTime)
每次迭代的时间(不是所有迭代的总时间)
只返回ns / iteration - 而不是元组
这是在这里实现的:
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
}
答案 9 :(得分:3)
ScalaMeter是一个很好的库,可以在Scala中执行基准测试
下面是一个简单的例子
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
答案 10 :(得分:1)
站在巨人的肩膀上......
一个可靠的第三方库会更理想,但如果您需要快速和基于std库的东西,以下变体提供:
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>
答案 11 :(得分:0)
您可以使用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
,因此很难看到。所以我建议您可以使用currentTimeMillis代替它。
答案 12 :(得分:0)
添加 => 带有名称和秒数的方法
profile[R](block: => R,methodName : String): R = {
val n = System.nanoTime()
val result = block
val n1 = System.nanoTime()
println(s"Elapsed time: ${TimeUnit.MILLISECONDS.convert(n1 - n,TimeUnit.NANOSECONDS)}ms - MethodName: ${methodName}")
result
}