如何在 Scala 中分析方法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9160001/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 03:52:02  来源:igfitidea点击:

How to profile methods in Scala?

functionscalaprofilingaspect

提问by sheki

What is a standard way of profiling Scala method calls?

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

What I need are hooks around a method, using which I can use to start and stop Timers.

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

In Java I use aspect programming, aspectJ, to define the methods to be profiled and inject bytecode to achieve the same.

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

Is there a more natural way in Scala, where I can define a bunch of functions to be called before and after a function without losing any static typing in the process?

在 Scala 中是否有更自然的方法,我可以在其中定义一堆函数在函数之前和之后调用,而不会在过程中丢失任何静态类型?

回答by Jesper

Do you want to do this without changing the code that you want to measure timings for? If you don't mind changing the code, then you could do something like this:

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

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 }

回答by oxbow_lakes

In addition to Jesper's answer, you can automatically wrap method invocations in the REPL:

除了 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

Now - let's wrap anything in this

现在 - 让我们把任何东西都包起来

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

OK - we need to be in power mode

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

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>    **

Wrap away

包裹起来

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

I have no idea why that printed stuff out 5 times

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

Update as of 2.12.2:

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

回答by missingfaktor

There are three benchmarking libraries for Scalathat you can avail of.

3个标杆库Scala的,你可以利用的。

Since the URLs on the linked site are likely to change, I am pasting the relevant content below.

由于链接网站上的网址可能会更改,因此我将粘贴以下相关内容。

  1. SPerformance- Performance Testing framework aimed at automagically comparing performance tests and working inside Simple Build Tool.

  2. scala-benchmarking-template- SBT template project for creating Scala (micro-)benchmarks based on Caliper.

  3. Metrics- Capturing JVM- and application-level metrics. So you know what's going on

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

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

  3. 指标- 捕获 JVM 和应用程序级别的指标。所以你知道发生了什么

回答by pathikrit

This what I use:

这是我使用的:

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)

回答by Luigi Plinge

testing.Benchmarkmight be useful.

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

回答by John Zhu

I took the solution from Jesper and added some aggregation to it on multiple run of the same code

我从 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))
}

Suppose you want to time two functions counter_newand counter_old, the following is the usage:

假设要对两个函数counter_newand计时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

Hopefully this is helpful

希望这有帮助

回答by Dharmesh

ScalaMeteris a nice library to perform benchmarking in Scala

ScalaMeter是一个很好的库,可以在 Scala 中执行基准测试

Below is a simple example

下面是一个简单的例子

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) }

If you execute above code snippet in Scala Worksheet you get the running time in milliseconds

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

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

回答by matanster

I use a technique that's easy to move around in code blocks. The crux is that the same exact line starts and ends the timer - so it is really a simple copy and paste. The other nice thing is that you get to define what the timing means to you as a string, all in that same line.

我使用一种很容易在代码块中移动的技术。关键是相同的行开始和结束计时器 - 所以它实际上是一个简单的复制和粘贴。另一个好处是您可以在同一行中将时间定义为字符串对您意味着什么。

Example usage:

用法示例:

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

The code:

代码:

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()
  }

Pros:

优点:

  • no need to wrap code as a block or manipulate within lines
  • can easily move the start and end of the timer among code lines when being exploratory
  • 无需将代码包装为块或在行内进行操作
  • 探索时可以轻松地在代码行之间移动计时器的开始和结束

Cons:

缺点:

  • less shiny for utterly functional code
  • obviously this object leaks map entries if you do not "close" timers, e.g. if your code doesn't get to the second invocation for a given timer start.
  • 对于完全功能性的代码来说不那么闪亮
  • 显然,如果您不“关闭”计时器,则该对象会泄漏映射条目,例如,如果您的代码没有进行给定计时器启动的第二次调用。

回答by Brent Faust

I like the simplicity of @wrick's answer, but also wanted:

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

  • the profiler handles looping (for consistency and convenience)

  • more accurate timing (using nanoTime)

  • time per iteration (not total time of all iterations)

  • just return ns/iteration - not a tuple

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

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

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

  • 只返回 ns/迭代 - 不是元组

This is achieved here:

这是在这里实现的:

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

For even more accuracy, a simple modification allows a JVM Hotspot warmup loop (not timed) for timing small snippets:

为了更准确,一个简单的修改允许 JVM Hotspot 预热循环(不计时)用于计时小片段:

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
}

回答by Darren Bishop

While standing on the shoulders of giants...

站在巨人的肩膀上...

A solid 3rd-party library would be more ideal, but if you need something quick and std-library based, following variant provides:

一个可靠的 3rd 方库会更理想,但如果您需要一些快速且基于标准库的东西,以下变体提供:

  • Repetitions
  • Last result winsfor multiple repetitions
  • Total time and average time for multiple repetitions
  • Removes the need for time/instant provider as a param
  • 重复
  • 多次重复的最后结果获胜
  • 多次重复的总时间和平均时间
  • 不需要时间/即时提供程序作为参数

.

.

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
  }
}

Also worth noting you can use the Duration.toCoarsestmethod to convert to the biggest time unit possible, although I am not sure how friendly this is with minor time difference between runs e.g.

同样值得注意的是,您可以使用该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>