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

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

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

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

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

106 голосов | спросил sheki 6 FebruaryEurope/MoscowbMon, 06 Feb 2012 16:19:01 +0400000000pmMon, 06 Feb 2012 16:19:01 +040012 2012, 16:19:01

10 ответов


0

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

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 6 FebruaryEurope/MoscowbMon, 06 Feb 2012 16:24:22 +0400000000pmMon, 06 Feb 2012 16:24:22 +040012 2012, 16:24:22
0

В дополнение к ответу Джеспера вы можете автоматически переносить вызовы методов в 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
ответил oxbow_lakes 6 FebruaryEurope/MoscowbMon, 06 Feb 2012 16:47:00 +0400000000pmMon, 06 Feb 2012 16:47:00 +040012 2012, 16:47:00
0

Существует три библиотеки сравнения для Scala что вы можете воспользоваться.

Поскольку URL-адреса на связанном сайте могут измениться, я добавляю соответствующий контент ниже.

  1. SPerformance - платформа для тестирования производительности, предназначенная для автоматического сравнения тестов производительности и работы внутри Simple Build. Инструмент.

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

  3. Метрики - сбор метрик JVM и приложений. Итак, вы знаете, что происходит

ответил missingfaktor 7 FebruaryEurope/MoscowbTue, 07 Feb 2012 00:17:56 +0400000000amTue, 07 Feb 2012 00:17:56 +040012 2012, 00:17:56
0

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

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)
ответил pathikrit 29 Jam1000000amTue, 29 Jan 2013 05:18:11 +040013 2013, 05:18:11
0

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
ответил Luigi Plinge 6 FebruaryEurope/MoscowbMon, 06 Feb 2012 17:24:25 +0400000000pmMon, 06 Feb 2012 17:24:25 +040012 2012, 17:24:25
0

Мне нравится простота ответа @ wrick, но я также хотел:

  • профилировщик обрабатывает циклы (для согласованности и удобства)

  • более точная синхронизация (с использованием 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
}
ответил Brent Faust 5 MaramThu, 05 Mar 2015 05:51:51 +03002015-03-05T05:51:51+03:0005 2015, 05:51:51
0

Я взял решение от 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

Надеюсь, это полезно

ответил John Zhu 10 AM000000120000000331 2017, 00:30:03
0

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
ответил Dharmesh 9 FriEurope/Moscow2016-12-09T02:16:44+03:00Europe/Moscow12bEurope/MoscowFri, 09 Dec 2016 02:16:44 +0300 2016, 02:16:44
0

Стоя на плечах великанов ...

Надежная сторонняя библиотека была бы более идеальной, но если вам нужно что-то быстрое и основанное на 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> 
ответил Darren Bishop 17 FebruaryEurope/MoscowbWed, 17 Feb 2016 14:37:10 +0300000000pmWed, 17 Feb 2016 14:37:10 +030016 2016, 14:37:10
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 вместо него.

ответил haiyang 27 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowTue, 27 Sep 2016 11:41:59 +0300 2016, 11:41:59

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132