function - the_tags - Cómo perfil de métodos en Scala?
wordpress get first tag (11)
¿Cuál es una forma estándar de crear perfiles de llamadas al método Scala?
Lo que necesito son ganchos alrededor de un método, con el cual puedo usar para iniciar y detener los temporizadores.
En Java utilizo la programación de aspecto, aspectJ, para definir los métodos que se van a perfilar e inyecto bytecode para lograr lo mismo.
¿Existe una forma más natural en Scala, donde puedo definir un conjunto de funciones para llamar antes y después de una función sin perder ningún tipo de tipado estático en el proceso?
¿Desea hacer esto sin cambiar el código para el que desea medir los tiempos? Si no te importa cambiar el código, entonces podrías hacer algo como esto:
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 }
Además de la respuesta de Jesper, puede ajustar automáticamente las invocaciones de métodos en 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
Ahora - envolvemos cualquier cosa en esto
scala> :wrap time
wrap: no such command. Type :help for help.
OK - tenemos que estar en el modo de potencia
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> **
Envolver
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
No tengo idea de por qué ese material impreso salió 5 veces
Actualización a 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
Esto es lo que uso:
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)
Hay tres bibliotecas de benchmarking para Scala de las que puede hacer uso.
Dado que es probable que cambien las URL en el sitio vinculado, pegaré el contenido relevante a continuación.
SPerformance : marco de pruebas de rendimiento para comparar SPerformance pruebas de rendimiento y trabajar dentro de Simple Build Tool.
scala-benchmarking-template - Proyecto de plantilla SBT para crear puntos de referencia Scala (micro-) basados en Caliper.
Metrics : captura de JVM y métricas a nivel de aplicación. Entonces sabes lo que está pasando
Me gusta la simplicidad de la respuesta de @ wrick, pero también quería:
el generador de perfiles maneja el bucle (por consistencia y conveniencia)
tiempo más preciso (usando nanoTime)
tiempo por iteración (no tiempo total de todas las iteraciones)
simplemente devuelve ns / iteración - no una tupla
Esto se logra aquí:
def profile[R] (repeat :Int)(code: => R, t: Long = System.nanoTime) = {
(1 to repeat).foreach(i => code)
(System.nanoTime - t)/repeat
}
Para una mayor precisión, una simple modificación permite un ciclo de calentamiento de punto de acceso JVM (no temporizado) para temporizar pequeños fragmentos:
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
}
Mientras está de pie sobre los hombros de gigantes ...
Una biblioteca sólida de terceros sería más ideal, pero si necesita algo rápido y basado en la biblioteca estándar, la siguiente variante ofrece:
- Repeticiones
- El último resultado gana por repeticiones múltiples
- Tiempo total y tiempo promedio para repeticiones múltiples
- Elimina la necesidad de tiempo / proveedor instantáneo como un param
.
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
}
}
También vale la pena señalar que puede utilizar el método Duration.toCoarsest
para convertir a la mayor unidad de tiempo posible, aunque no estoy seguro de lo fácil que es esto con una pequeña diferencia de tiempo entre ejecuciones, por ejemplo
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>
Puede usar 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
}
Uso:
time{
//execute somethings here, like methods, or some codes.
}
nanoTime te mostrará ns
, por lo que será difícil de ver. Por lo tanto, le sugiero que puede usar currentTimeMillis en lugar de hacerlo.
Tomé la solución de Jesper y agregué una agregación a ella en varias ejecuciones del mismo código
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))
}
Supongamos que desea counter_new
dos funciones counter_new
y counter_old
, el siguiente es el uso:
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
Espero que esto sea útil
Utilizo una técnica que es fácil de mover en bloques de código. El quid de la cuestión es que comienza la misma línea exacta y finaliza el temporizador, por lo que en realidad es una simple copia y pegado. La otra cosa agradable es que puedes definir lo que el tiempo significa para ti como una cadena, todo en la misma línea.
Ejemplo de uso:
Timelog("timer name/description")
//code to time
Timelog("timer name/description")
El código:
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()
}
Pros:
- no es necesario ajustar el código como un bloque o manipular dentro de las líneas
- puede mover fácilmente el inicio y el final del temporizador entre las líneas de código al ser exploratorio
Contras:
- menos brillante para el código completamente funcional
- Obviamente, este objeto filtra las entradas del mapa si no "cierra" los temporizadores, por ejemplo, si su código no llega a la segunda invocación para un inicio de temporizador determinado.
ScalaMeter es una buena biblioteca para realizar benchmarking en Scala
A continuación se muestra un ejemplo 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 ejecuta el fragmento de código anterior en la hoja de trabajo de Scala obtendrá el tiempo de ejecución en milisegundos
execution_time: org.scalameter.Quantity[Double] = 0.260325 ms
testing.Benchmark
podría ser útil.
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