Как профилировать методы в Scala?
каков стандартный способ профилирования вызовов метода Scala?
Мне нужны крючки вокруг способ, с помощью которого я могу использовать для запуска и остановки таймеров.
в Java я использую аспектное Программирование, aspectJ, чтобы определить методы, которые будут профилироваться и вводить байт-код для достижения того же самого.
есть ли более естественный способ в Scala, где я могу определить кучу функций, которые будут вызываться до и после функции без потери статического ввода в процесс?
11 ответов:
вы действительно хотите сделать это без изменения кода, который вы хотите измерить время? Если вы не возражаете, изменяя код, то вы могли бы сделать что-то вроде этого:
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 }
В дополнение к ответу Йеспера, вы можете автоматически обернуть вызовы метода в 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-адреса на связанном сайте, скорее всего, изменятся, я вставляю соответствующий контент ниже.
SPerformance - платформа тестирования производительности, направленная на автоматическое сравнение тестов производительности и работу внутри простого инструмента сборки.
scala-бенчмаркинг-шаблон - проект шаблона SBT для создания Scala (микро-)бенчмарков на основе суппорта.
показатели - захват метрик уровня 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
Я использую технику, которая легко перемещается в блоках кода. Суть в том, что одна и та же точная строка начинает и заканчивает таймер - так что это действительно простая копия и вставка. Другая приятная вещь заключается в том, что вы можете определить, что время означает для вас как строка, все в той же строке.
пример использования:
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() }
плюсы:
- нет необходимости обертывать код как блок или манипулировать внутри линии
- может легко перемещать начало и конец таймера между строками кода, когда он является исследовательским
плюсы:
- менее блестящий для совершенно функционального кода
- очевидно, что этот объект пропускает записи карты, если вы не" закрываете " таймеры, например, если ваш код не попадает во второй вызов для данного запуска таймера.
мне нравится простота ответа @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 warmup loop (не синхронизированный) для синхронизации небольших фрагментов:
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 }
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 вы получаете время выполнения в миллисекундах
execution_time: org.scalameter.Quantity[Double] = 0.260325 ms
Я взял решение от Йеспера и добавил к нему некоторую агрегацию при многократном запуске одного и того же кода
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
надеюсь, это полезно
стоя на плечах гигантов...
твердая сторонняя библиотека была бы более идеальной, но если вам нужно что-то быстрое и основанное на std-библиотеке, следующий вариант обеспечивает:
- повторений
- последний результат победы для многократных повторений
- Общее время и среднее время многократных повторений
- устраняет необходимость во времени / мгновенном провайдере в качестве параметр
.
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>
можно использовать
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 вместо него.