2012-02-06 69 views
98

什麼是剖析Scala方法調用的標準方法?如何在Scala中剖析方法?

我需要的是一個方法的鉤子,我可以用它來啓動和停止定時器。

在Java中,我使用方面編程aspectJ來定義要分析的方法並注入字節碼以實現相同。

在Scala中有沒有更自然的方法,在那裏我可以定義一堆函數在函數前後被調用,而不會在程序中丟失任何靜態類型?

+0

如果AspectJ的使用Scala很好地發揮,用AspectJ。爲什麼重新發明輪子?以上使用自定義流量控制的答案無法達到AOP的基本要求,因爲使用它們需要修改代碼。這些也可能是有趣的:http://java.dzone.com/articles/real-world-scala-managing-cros http://blog.fakod.eu/2010/07/26/cross-cutting-concerns- in-scala/ – 2012-02-19 14:56:25

+0

- https://chariotsolutions.com/blog/post/microbenchmarking-scala-with-jmh/ - https://github.com/wadejensen/sbt-jmh-example - https:// github .com/ktoso/sbt-jmh – 2017-12-29 03:49:55

回答

171

是否要在不更改要測量計時的代碼的情況下執行此操作?如果你不介意改變代碼,那麼你可以做這樣的事情:

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

這很整潔,我可以在沒有任何代碼更改的情況下做同樣的事情嗎? – sheki 2012-02-06 12:34:56

+0

不自動使用此解決方案;斯卡拉怎麼知道你想要的時間? – Jesper 2012-02-06 12:38:37

+1

這並非嚴格意義上的 - 您可以自動將某些內容包裝到REPL – 2012-02-06 12:43:46

31

除了加斯帕的回答,您可以自動換行的方法調用在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 
+6

爲了免除任何人現在想知道的麻煩,':wrap'功能[已被刪除](https://groups.google.com/forum/#!msg/scala-user/cE-z6fxkq0U/ EszYr_19qgMJ): - \ – ches 2016-05-04 16:28:08

+1

@ches參閱'$ intp.setExecutionWrapper'的更新。 – 2017-06-01 16:55:06

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

請注意,testing.Benchmark是@deprecated(「此類將被刪除。」,「2.10.0」)。 – Tvaroh 2013-08-23 21:37:38

18

這是我使用:

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

我使用的技術,很容易在代碼塊左右移動。關鍵是相同的確切線開始和結束計時器 - 所以它是一個簡單的複製和粘貼。另一件好事是,你可以定義什麼時間對你來說意味着一個字符串,都在同一行。

實例:

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

優點:

  • 不需要包裹代碼作爲一個塊或行內操縱
  • 可以容易地移動當被探測時,代碼行中定時器的開始和結束ORY

缺點:

  • 少光澤的完全功能代碼
  • 顯然這個對象泄漏映射條目,如果你沒有「關閉」定時器, 例如如果你的代碼沒有得到給定定時器啓動的第二次調用。
+0

這很好,但不應該是:'Timelog.timer(「timer name/description」)'? – schoon 2017-09-11 14:48:48

3

我喜歡@扭傷的回答簡單,但也想:

  • 探查處理循環(一致性和便利)

  • 更準確的計時(使用nanoTime)

  • 每次迭代的時間(不是所有迭代的總時間)

  • 只返回NS /迭代 - 不是一個元組

這是在這裏實現:

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

這不是一個答案,最好是將它作爲註釋寫入 – nedim 2015-11-27 12:13:51

+1

@nedim解決方法是給予這個問題的 - 一個你想要的東西的包裝。OP想要調用的任何函數都可以放在包裝器中,或放在調用其函數的塊中,以便「可以定義一組要在函數前後調用的函數,而不會丟失任何靜態類型」 – 2015-12-01 00:47:32

+1

對。對不起,我一定忽略了代碼。當我的編輯得到審查時,我可以撤消downvote。 – nedim 2015-12-01 08:39:48

1

而站在巨人的肩膀上......

實第三方庫會比較理想,但如果你需要根據東西快速和std庫,以下變型提供:

  • 重名
  • 最後結果贏得的多次重複
  • 總時間和平均時間多次重複
  • 刪除需要時間/即時提供商爲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 
    } 
} 

另外值得注意的您可以使用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> 
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來代替它。

+0

難以辨認的納秒很難在兩者之間選擇。除了分辨率之外還有一些重要的差異首先,currentTimeMillis可以在OS週期性執行的時鐘調整期間改變甚至倒退。另一個是nanoTime可能不是線程安全的:http://stackoverflow.com/questions/351565/system-currenttimemillis-vs-system-nanotime – Chris 2016-10-12 00:07:58

2

ScalaMeter是在斯卡拉執行基準一個很好的圖書館

下面是一個簡單的例子

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的工作表的代碼片段你以毫秒爲單位的運行時間

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

我從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_newcounter_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 

希望這是有幫助的