127

Scalaメソッド呼び出しをプロファイリングする標準的な方法は何ですか?

必要なのは、タイマーを開始および停止するために使用できるメソッドのフックです。

Javaでは、アスペクトプログラミングaspectJを使用して、プロファイリングするメソッドを定義し、同じことを実現するためにバイトコードを挿入します。

Scalaには、プロセスで静的型付けを失うことなく、関数の前後に呼び出される一連の関数を定義できる、より自然な方法がありますか?

4

13 に答える 13

34

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.

OK - パワーモードにする必要があります

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
于 2012-02-06T12:47:00.317 に答える
25

利用できるScala 用のベンチマーク ライブラリは 3 つあります。

リンク先のURLは変更になる可能性が高いので、関連する内容を以下に貼り付けます。

  1. SPerformance - パフォーマンス テストを自動的に比較し, Simple Build Tool 内で作業することを目的としたパフォーマンス テスト フレームワーク.

  2. scala-benchmarking-template - Caliper に基づいて Scala (マイクロ) ベンチマークを作成するための SBT テンプレート プロジェクト.

  3. メトリクス- JVM およびアプリケーション レベルのメトリクスをキャプチャします。だから、何が起こっているか知っている

于 2012-02-06T20:17:56.597 に答える
25

これは私が使用するものです:

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)
于 2013-01-29T01:18:11.503 に答える
4

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.
于 2014-09-18T23:28:38.140 に答える
3

@wrickの答えのシンプルさが好きですが、次のことも望んでいました:

  • プロファイラーはループを処理します (一貫性と利便性のため)

  • より正確なタイミング (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
}
于 2015-03-05T02:51:51.640 に答える
1

巨人の肩の上に立ちながら…

しっかりしたサードパーティのライブラリがより理想的ですが、迅速で標準ライブラリベースのものが必要な場合は、次のバリアントが提供されます。

  • 繰り返し
  • 最後の結果が複数回の繰り返しの勝利
  • 複数の繰り返しの合計時間と平均時間
  • パラメーターとして時間/インスタントプロバイダーの必要性を取り除きます

.

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> 
于 2016-02-17T11:37:10.487 に答える