function - документация - diafan это




Как профилировать методы в Scala? (8)

Каков стандартный способ профилирования вызовов метода Scala?

Мне нужны крючки вокруг метода, используя которые я могу использовать для запуска и остановки таймеров.

В Java я использую аспектное программирование, aspectJ, чтобы определить методы для профилирования и вставить байт-код для достижения того же.

Есть ли более естественный способ в Scala, где я могу определить кучу функций, которые будут вызываться до и после функции без потери статического ввода в процессе?


В дополнение к ответу Йеспера вы можете автоматически переносить вызовы методов в 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.

ОК - нам нужно быть в режиме питания

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 - Performance Testing, нацеленная на автоматическое сравнение тестов производительности и работы внутри Simple Build Tool.

  2. scala-benchmarking-template - проект scala-benchmarking-template SBT для создания тестов Scala (micro-) на основе суппорта.

  3. Metrics - Захват Metrics JVM- и уровня приложения. Значит, вы знаете, что происходит


Вы хотите сделать это, не изменяя код, для которого вы хотите измерить тайминги? Если вы не возражаете изменить код, вы можете сделать что-то вроде этого:

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 }

Мне нравится простота ответа @ 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 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
}

Это то, что я использую:

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)

Я использую технику, которая легко перемещается в блоках кода. Суть в том, что та же самая точная линия начинается и заканчивается таймером - так что это действительно простая копия и вставка. Другая приятная вещь заключается в том, что вы можете определить, что время означает для вас как строку, все в той же строке.

Пример использования:

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

Плюсы:

  • нет необходимости обертывать код как блок или манипулировать внутри строк
  • может легко перемещать начало и конец таймера между кодовыми линиями при проведении поисковых

Минусы:

  • менее блестящий для абсолютно функционального кода
  • очевидно, что этот объект теряет карты, если вы не закрываете таймеры, например, если ваш код не попадает во второй вызов для заданного запуска таймера.

ScalaMeter - хорошая библиотека для проведения бенчмаркинга в Scala

Ниже приведен простой пример

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 Worksheet, вы получите время работы в миллисекундах

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

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




aspect