如何在Scala中分析方法?

she*_*eki 115 profiling scala function aspect

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

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

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

在Scala中是否有更自然的方式,我可以在函数前后定义一组函数,而不会在进程中丢失任何静态类型?

Jes*_*per 207

您是否希望在不更改要测量时序的代码的情况下执行此操作?如果您不介意更改代码,那么您可以执行以下操作:

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 }
Run Code Online (Sandbox Code Playgroud)

  • 您可以使用一些柯里化为您的打印添加标签:`def time[R](label: String)(block: => R): R = {` 然后将标签添加到 `println` (3认同)
  • 几乎完美,但您也必须对可能的异常做出反应。在“finally”子句中计算“t1” (2认同)

oxb*_*kes 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
Run Code Online (Sandbox Code Playgroud)

现在 - 让我们在这里包装任何内容

scala> :wrap time
wrap: no such command.  Type :help for help.
Run Code Online (Sandbox Code Playgroud)

好的 - 我们需要处于电源模式

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>    **
Run Code Online (Sandbox Code Playgroud)

包裹起来

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
Run Code Online (Sandbox Code Playgroud)

我不知道为什么那个印刷的东西出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
Run Code Online (Sandbox Code Playgroud)

  • 为了保全人现在想知道的麻烦,`:wrap`功能[移除](https://groups.google.com/forum/#!msg/scala-user/cE-z6fxkq0U/EszYr_19qgMJ)从REPL: - \ (8认同)
  • @ches参见`$ intp.setExecutionWrapper`的更新. (2认同)

mis*_*tor 23

斯卡拉3个标杆库,你可以利用的.

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

  1. 性能 - 性能测试框架旨在自动比较性能测试和在Simple Build Tool中工作.

  2. scala-benchmarking-template - 用于创建基于Caliper的Scala(微)基准测试的SBT模板项目.

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


pat*_*rit 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)
Run Code Online (Sandbox Code Playgroud)


Lui*_*hys 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
Run Code Online (Sandbox Code Playgroud)

  • 请注意,testing.Benchmark是@deprecated("此类将被删除.","2.10.0"). (5认同)

mat*_*ter 6

我使用了一种易于在代码块中移动的技术。关键在于完全相同的行开始和结束计时器 - 所以这实际上是一个简单的复制和粘贴。另一个好处是,您可以将时间对您的意义定义为字符串,所有这些都在同一行中。

用法示例:

Timelog("timer name/description")
//code to time
Timelog("timer name/description")
Run Code Online (Sandbox Code Playgroud)

代码:

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()
  }
Run Code Online (Sandbox Code Playgroud)

优点:

  • 无需将代码包装为块或在行内进行操作
  • 在探索时可以轻松地在代码行之间移动计时器的开始和结束

缺点:

  • 对于功能齐全的代码来说不太闪亮
  • 显然,如果您不“关闭”计时器,例如,如果您的代码未到达给定计时器启动的第二次调用,则该对象会泄漏映射条目。

  • 这很棒,但用法不应该是:`Timelog.timer("定时器名称/描述")`吗? (2认同)

Dha*_*esh 6

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) }
Run Code Online (Sandbox Code Playgroud)

如果您在 Scala Worksheet 中执行上述代码片段,您将获得以毫秒为单位的运行时间

execution_time: org.scalameter.Quantity[Double] = 0.260325 ms
Run Code Online (Sandbox Code Playgroud)


Mar*_*lic 6

推荐的 Scala 代码基准测试方法是通过sbt-jmh

\n\n
\n

“不要相信任何人,把一切都放在板凳上。” - JMH 的 sbt 插件(Java\n Microbenchmark Harness)

\n
\n\n

许多主要的 Scala 项目都采用了这种方法,例如,

\n\n\n\n

基于简单包装定时器的方法System.nanoTime并不是可靠的方法

\n\n
\n

System.nanoTime和现在一样糟糕String.intern:你可以使用它,但要明智地使用它。如果没有适当的严格性,计时器带来的延迟、粒度和可扩展性影响可能会影响您的测量。这是众多原因之一\n System.nanoTime这是为什么\n应通过基准测试\n 框架从用户那里抽象出来的

\n
\n\n

此外,诸如JIT 预热、垃圾收集、系统范围事件等考虑因素可能会带来不可预测性会给测量

\n\n
\n

需要减轻大量影响,包括预热、死代码消除、分叉等。幸运的是,JMH 已经处理了许多事情,并且具有 Java 和 Scala 的绑定。

\n
\n\n

基于特拉维斯·布朗的回答,这里是一个例子如何为 Scala 设置 JMH 基准的

\n\n
    \n
  1. 将 jmh 添加到project/plugins.sbt\n\n
    addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7")\n
    Run Code Online (Sandbox Code Playgroud)
  2. \n
  3. 启用 jmh 插件build.sbt\n\n
    enablePlugins(JmhPlugin)\n
    Run Code Online (Sandbox Code Playgroud)
  4. \n
  5. 添加src/main/scala/bench/VectorAppendVsListPreppendAndReverse.scala

    \n\n
    package bench\n\nimport org.openjdk.jmh.annotations._\n\n@State(Scope.Benchmark)\n@BenchmarkMode(Array(Mode.AverageTime))\nclass VectorAppendVsListPreppendAndReverse {\n  val size = 1_000_000\n  val input = 1 to size\n\n  @Benchmark def vectorAppend: Vector[Int] = \n    input.foldLeft(Vector.empty[Int])({ case (acc, next) => acc.appended(next)})\n\n  @Benchmark def listPrependAndReverse: List[Int] = \n    input.foldLeft(List.empty[Int])({ case (acc, next) => acc.prepended(next)}).reverse\n}\n
    Run Code Online (Sandbox Code Playgroud)
  6. \n
  7. 使用 \n\n 执行基准测试
    sbt "jmh:run -i 10 -wi 10 -f 2 -t 1 bench.VectorAppendVsListPreppendAndReverse"\n
    Run Code Online (Sandbox Code Playgroud)
  8. \n
\n\n

结果是

\n\n
Benchmark                                                   Mode  Cnt  Score   Error  Units\nVectorAppendVsListPreppendAndReverse.listPrependAndReverse  avgt   20  0.024 \xc2\xb1 0.001   s/op\nVectorAppendVsListPreppendAndReverse.vectorAppend           avgt   20  0.130 \xc2\xb1 0.003   s/op\n
Run Code Online (Sandbox Code Playgroud)\n\n

这似乎表明先添加到 aList然后在最后反转它比继续添加到 a 快一个数量级Vector

\n


小智 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))
}
Run Code Online (Sandbox Code Playgroud)

假设您想对两个函数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
Run Code Online (Sandbox Code Playgroud)

希望这会有所帮助