Scalaメソッド呼び出しをプロファイリングする標準的な方法は何ですか?
必要なのは、タイマーを開始および停止するために使用できるメソッドのフックです。
Javaでは、アスペクトプログラミングaspectJを使用して、プロファイリングするメソッドを定義し、同じことを実現するためにバイトコードを挿入します。
Scalaには、プロセスで静的型付けを失うことなく、関数の前後に呼び出される一連の関数を定義できる、より自然な方法がありますか?
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
利用できるScala 用のベンチマーク ライブラリは 3 つあります。
リンク先のURLは変更になる可能性が高いので、関連する内容を以下に貼り付けます。
SPerformance - パフォーマンス テストを自動的に比較し, Simple Build Tool 内で作業することを目的としたパフォーマンス テスト フレームワーク.
scala-benchmarking-template - Caliper に基づいて Scala (マイクロ) ベンチマークを作成するための SBT テンプレート プロジェクト.
メトリクス- JVM およびアプリケーション レベルのメトリクスをキャプチャします。だから、何が起こっているか知っている
これは私が使用するものです:
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)
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:
Cons:
@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
}
巨人の肩の上に立ちながら…
しっかりしたサードパーティのライブラリがより理想的ですが、迅速で標準ライブラリベースのものが必要な場合は、次のバリアントが提供されます。
.
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>