[function] Scala에서 메서드를 프로파일 링하는 방법은 무엇입니까?

Scala 메서드 호출을 프로파일 링하는 표준 방법은 무엇입니까?

내가 필요한 것은 메소드를 둘러싼 후크이며,이를 사용하여 타이머를 시작하고 중지 할 수 있습니다.

Java에서는 aspect 프로그래밍, aspectJ를 사용하여 프로파일 링 할 메소드를 정의하고 동일한 작업을 수행하기 위해 바이트 코드를 삽입합니다.

Scala에 더 자연스러운 방법이 있습니까? 프로세스에서 정적 유형을 잃지 않고 함수 전후에 호출 할 함수를 정의 할 수 있습니까?



답변

타이밍을 측정 할 코드를 변경하지 않고이 작업을 수행 하시겠습니까? 코드를 변경해도 괜찮다면 다음과 같이 할 수 있습니다.

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 }


답변

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 용 벤치마킹 라이브러리 에는 세 가지 가 있습니다 .

링크 된 사이트의 URL이 변경 될 가능성이 있으므로 아래에 관련 내용을 붙여 넣습니다.

  1. SPerformance- 성능 테스트를 자동으로 비교하고 Simple Build Tool 내에서 작업하는 것을 목표로하는 성능 테스트 프레임 워크입니다.

  2. scala-benchmarking-template- Caliper를 기반으로 Scala (마이크로) 벤치 마크를 만들기위한 SBT 템플릿 프로젝트입니다.

  3. 메트릭 -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)


답변

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


답변

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

도움이 되었기를 바랍니다.


답변

코드 블록에서 이동하기 쉬운 기술을 사용합니다. 핵심은 똑같은 라인이 타이머를 시작하고 종료한다는 것입니다. 따라서 정말 간단한 복사 및 붙여 넣기입니다. 또 다른 좋은 점은 타이밍이 당신에게 의미하는 바를 문자열로 정의 할 수 있다는 것입니다.

사용 예 :

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

장점 :

  • 코드를 블록으로 감싸거나 줄 내에서 조작 할 필요가 없습니다.
  • 탐색 중일 때 코드 라인 사이에서 타이머의 시작과 끝을 쉽게 이동할 수 있습니다.

단점 :

  • 완전히 기능적인 코드에 대해 덜 반짝임
  • 분명히이 객체는 타이머를 “닫지”않으면 맵 항목을 누출합니다. 예를 들어 코드가 주어진 타이머 시작에 대한 두 번째 호출에 도달하지 않는 경우.