Qu'est-ce qu'une méthode standard de profilage Scala?
Ce dont j'ai besoin, ce sont des crochets autour d'une méthode, que je peux utiliser pour démarrer et arrêter des minuteries.
Dans Java j'utilise la programmation d'aspects, aspectJ, pour définir les méthodes à profiler et injecter du bytecode pour obtenir le même résultat.
Existe-t-il une méthode plus naturelle dans Scala, dans laquelle je peux définir un ensemble de fonctions à appeler avant et après une fonction sans perdre de typage statique dans le processus?
Voulez-vous faire cela sans changer le code pour lequel vous voulez mesurer les timings? Si cela ne vous dérange pas de changer le code, vous pouvez faire quelque chose comme ceci:
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 }
En plus de la réponse de Jesper, vous pouvez encapsuler automatiquement les invocations de méthodes dans le 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
Maintenant, passons à autre chose
scala> :wrap time
wrap: no such command. Type :help for help.
OK - nous devons être en mode puissance
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> **
Envelopper
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
Je ne sais pas pourquoi ces trucs imprimés sont sortis 5 fois
Mise à jour à partir de 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
Il existe trois bibliothèques d'analyse comparative pour Scala dont vous pouvez vous prévaloir.
Étant donné que les URL sur le site lié sont susceptibles de changer, je colle le contenu pertinent ci-dessous.
SPerformance - Structure de test de performance visant à comparer automatiquement les tests de performance et à fonctionner dans Simple Build Tool.
scala-benchmarking-template - Projet de modèle SBT permettant de créer Scala (micro-)) critères de référence basés sur Caliper.
Metrics - Capture des métriques JVM et au niveau de l'application. Donc vous savez ce qui se passe
C'est ce que j'utilise:
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
pourrait être utile.
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
J'ai pris la solution de Jesper et y ai ajouté un peu d'agrégation sur plusieurs exécutions du même code
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))
}
Supposons que vous vouliez chronométrer deux fonctions counter_new
et counter_old
, voici l’utilisation:
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
J'espère que c'est utile
J'utilise une technique facile à déplacer dans des blocs de code. Le point crucial est que la même ligne exacte commence et finit le chronomètre - il s’agit donc d’un simple copier-coller. L’autre atout, c’est que vous définissez ce que le timing signifie pour vous en tant que chaîne, le tout dans la même ligne.
Exemple d'utilisation:
Timelog("timer name/description")
//code to time
Timelog("timer name/description")
Le code:
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()
}
Avantages:
Les inconvénients:
J'aime la simplicité de la réponse de @ wrick, mais je voulais aussi:
le profileur gère les boucles (pour la cohérence et la commodité)
chronométrage plus précis (avec nanoTime)
temps par itération (pas le temps total de toutes les itérations)
retourne juste ns/itération - pas un tuple
Ceci est réalisé ici:
def profile[R] (repeat :Int)(code: => R, t: Long = System.nanoTime) = {
(1 to repeat).foreach(i => code)
(System.nanoTime - t)/repeat
}
Pour encore plus de précision, une simple modification permet à une boucle d'échauffement JVM Hotspot (non temporisée) de chronométrer de petits extraits:
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 est une bibliothèque de Nice pour effectuer des analyses comparatives dans Scala
Ci-dessous un exemple simple
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) }
Si vous exécutez l'extrait de code ci-dessus dans Scala Feuille de calcul, vous obtenez le temps d'exécution en millisecondes
execution_time: org.scalameter.Quantity[Double] = 0.260325 ms
Vous pouvez utiliser 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
}
Usage:
time{
//execute somethings here, like methods, or some codes.
}
nanoTime vous montrera ns
, il sera donc difficile de voir. Je suggère donc que vous puissiez utiliser currentTimeMillis à la place.
En se tenant sur les épaules de géants ...
Une bibliothèque tierce solide serait l'idéal, mais si vous avez besoin de quelque chose de rapide et basé sur la bibliothèque std, la variante suivante fournit:
.
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
}
}
À noter également que vous pouvez utiliser le Duration.toCoarsest
méthode pour convertir en unité de temps la plus grande possible, bien que je ne sois pas sûr que ce soit convivial, avec une différence de temps minime entre les exécutions, par exemple.
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>