स्केला में प्रोफाइल कैसे करें?


117

स्केल विधि कॉल को प्रोफाइल करने का एक मानक तरीका क्या है?

मुझे एक विधि के चारों ओर हुक की आवश्यकता है, जिसके उपयोग से मैं टाइमर शुरू करने और बंद करने के लिए उपयोग कर सकता हूं।

जावा में मैं पहलू प्रोग्रामिंग का उपयोग करता हूं, उसी को प्राप्त करने के लिए प्रोफाइलटेक और इंजेक्ट किए जाने वाले तरीकों को परिभाषित करने के लिए एस्पैक्ट जे।

क्या स्केला में एक और अधिक प्राकृतिक तरीका है, जहां मैं किसी फ़ंक्शन को प्रक्रिया में किसी भी स्थिर टाइपिंग को खोए बिना पहले और बाद में बुलाया जाने वाले कार्यों का एक गुच्छा परिभाषित कर सकता हूं?


यदि AspectJ Scala के साथ अच्छी तरह से खेलता है, तो AspectJ का उपयोग करें। पहिया को क्यों मजबूत करें? ऊपर दिए गए उत्तर जो कस्टम फ्लो कंट्रोल का उपयोग करते हैं, AOP की मूलभूत आवश्यकताओं को प्राप्त करने में विफल होते हैं क्योंकि उनका उपयोग करने के लिए आपको अपने कोड को संशोधित करने की आवश्यकता होती है। ये भी रुचि के हो सकते हैं: java.dzone.com/articles/real-world-scala-managing-cros blog.fakod.eu/2010/07/26/cross-cutting-concerns-in-scala
Ant


आप किस चीज़ में रुचि रखते हैं? क्या आप जानना चाहते हैं कि उत्पादन वातावरण में एक निश्चित विधि को कितना समय लगता है। फिर आपको मेट्रिक्स लाइब्रेरी देखनी चाहिए और स्वीकार किए गए उत्तर में खुद को मापना नहीं चाहिए। यदि आप जांचना चाहते हैं कि कौन सा कोड वेरिएंट "सामान्य रूप से" है, यानी आपके विकास के माहौल में, नीचे प्रस्तुत sbt-jmh का उपयोग करें।
jmg

जवाबों:


214

क्या आप उस कोड को बदले बिना ऐसा करना चाहते हैं जिसे आप समय के लिए मापना चाहते हैं? यदि आपको कोड बदलने में कोई आपत्ति नहीं है, तो आप कुछ इस तरह से कर सकते हैं:

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 }

यह साफ-सुथरा है, क्या मैं बिना किसी कोड परिवर्तन के एक ही काम कर सकता हूं?
शकी

इस समाधान के साथ स्वचालित रूप से नहीं; स्काला को कैसे पता चलेगा कि आप क्या करना चाहते हैं?
जेस्पर

1
यह कड़ाई से सच नहीं है - आप स्वचालित रूप से REPL में चीजों को लपेट सकते हैं
६’१२ को oxbow_lakes

1
लगभग सही, लेकिन आपको संभावित अपवादों पर भी प्रतिक्रिया करनी चाहिए। t1एक finallyक्लॉज के भीतर की गणना करें
जुआनमीरोक्स

2
आप कुछ करीने से अपने प्रिंट में एक लेबल जोड़ सकते हैं: def time[R](label: String)(block: => R): R = {फिर लेबल जोड़ेंprintln
ग्लेन 'देवलास'

34

जेस्पर के जवाब के अलावा, आप स्वचालित रूप से 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

8
अब किसी को भी आश्चर्यचकित करने की परेशानी को दूर करने के लिए, REPL से :wrapफीचर हटा दिया गया : - \
चेस

25

हैं स्काला के लिए तीन बेंच मार्किंग पुस्तकालयों है कि आप का लाभ उठाने कर सकते हैं।

चूंकि लिंक की गई साइट के URL में परिवर्तन होने की संभावना है, इसलिए मैं नीचे दी गई संबंधित सामग्री को चिपका रहा हूं।

  1. SPerformance - प्रदर्शन परीक्षण रूपरेखा का उद्देश्य स्वचालित रूप से प्रदर्शन परीक्षणों की तुलना करना और सरल बिल्ड टूल के अंदर काम करना है।

  2. scala- बेंचमार्किंग-टेम्प्लेट - कैलिपर पर आधारित Scala (माइक्रो-) बेंचमार्क बनाने के लिए SBT टेम्प्लेट प्रोजेक्ट।

  3. मेट्रिक्स - जेवीएम- और एप्लिकेशन-स्तरीय मैट्रिक्स पर कब्जा करना। तो आप जानते हैं कि क्या चल रहा है


21

यह मैं क्या उपयोग करें:

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)

6

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

5
ध्यान रखें कि परीक्षण। Benchmark @deprecated है ("यह वर्ग हटा दिया जाएगा।", "2.10.0")।
तवरोह

5

मैंने जेसपर से समाधान लिया और उसी कोड के कई रन पर कुछ एकत्रीकरण जोड़ा

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

उम्मीद है कि यह उपयोगी है


4

मैं एक तकनीक का उपयोग करता हूं जो कोड ब्लॉक में घूमना आसान है। क्रूक्स यह है कि एक ही सटीक रेखा टाइमर को शुरू और समाप्त करती है - इसलिए यह वास्तव में एक सरल कॉपी और पेस्ट है। दूसरी अच्छी बात यह है कि आपको यह परिभाषित करना है कि टाइमिंग का मतलब क्या है एक स्ट्रिंग के रूप में, यह सब उसी लाइन में है।

उदाहरण उपयोग:

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

पेशेवरों:

  • कोड को ब्लॉक के रूप में लपेटने या लाइनों के भीतर हेरफेर करने की कोई आवश्यकता नहीं है
  • खोजकर्ता होने पर आसानी से कोड लाइनों के बीच टाइमर के प्रारंभ और अंत को स्थानांतरित कर सकते हैं

विपक्ष:

  • पूरी तरह से कार्यात्मक कोड के लिए कम चमकदार
  • यदि आप अपने कोड को दिए गए टाइमर की शुरुआत के लिए दूसरे आह्वान पर नहीं आते हैं, तो जाहिर है, यह ऑब्जेक्ट मैप प्रविष्टियों को लीक करता है, जैसे कि "क्लोज़" टाइमर्स नहीं।

यह बहुत अच्छा है, लेकिन इसका उपयोग नहीं होना चाहिए Timelog.timer("timer name/description"):?
schoon

4

स्कालामीटर स्कैला में बेंचमार्किंग करने के लिए एक अच्छी लाइब्रेरी है

नीचे एक सरल उदाहरण है

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

3

मुझे @ रेकी के उत्तर की सादगी पसंद है, लेकिन यह भी चाहता था:

  • प्रोफाइलर लूपिंग संभालता है (स्थिरता और सुविधा के लिए)

  • अधिक सटीक समय (नैनो टाइम का उपयोग करके)

  • पुनरावृत्ति का समय (सभी पुनरावृत्तियों का कुल समय नहीं)

  • बस ns / पुनरावृति लौटें - टपल नहीं

यह यहाँ प्राप्त किया गया है:

def profile[R] (repeat :Int)(code: => R, t: Long = System.nanoTime) = { 
  (1 to repeat).foreach(i => code)
  (System.nanoTime - t)/repeat
}

और भी अधिक सटीकता के लिए, एक साधारण संशोधन छोटे स्निपिंग टाइमिंग के लिए एक जेवीएम हॉटस्पॉट वार्मअप लूप (समय पर नहीं) की अनुमति देता है:

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
}

यह एक उत्तर नहीं है, इसे टिप्पणी के रूप में लिखना सबसे अच्छा होगा
nedim

1
@nim प्रश्न का हल दिया गया है - किसी भी चीज़ के लिए एक रैपर जो आप चाहते हैं। किसी भी फ़ंक्शन को कॉल करने के लिए ओपी चाहेगा, उसे रैपर में रखा जा सकता है, या उसके कार्यों को कॉल करने वाले ब्लॉक में, ताकि वह "किसी भी स्थिर टाइपिंग को खोए बिना एक फ़ंक्शन से पहले और बाद में बुलाए जाने वाले कार्यों का एक गुच्छा परिभाषित कर सके"
ब्रेंट फास्ट

1
तुम सही हो। क्षमा करें, मुझे कोड की अनदेखी करनी चाहिए थी। जब मेरे संपादन की समीक्षा हो जाती है तो मैं डाउनवोट को पूर्ववत कर सकता हूं।
नादिम

3

स्केला कोड को बेंचमार्क करने के लिए अनुशंसित दृष्टिकोण sbt-jmh के माध्यम से है

"किसी पर भरोसा न करें, सब कुछ बेंच दें।" - JMH के लिए sbt प्लगइन (जावा माइक्रोबेंचमार्क हार्नेस)

यह दृष्टिकोण कई प्रमुख स्काला परियोजनाओं द्वारा लिया गया है, उदाहरण के लिए,

के आधार पर सरल आवरण घड़ी System.nanoTimeहै एक विश्वसनीय तरीका नहीं बेंचमार्किंग की:

System.nanoTimeयह String.internअब जितना बुरा है : आप इसका उपयोग कर सकते हैं, लेकिन इसे बुद्धिमानी से उपयोग करें। समयावधि द्वारा पेश की जाने वाली विलंबता, ग्रेन्युलैरिटी, और स्केलेबिलिटी प्रभाव, उचित कठोरता के बिना आपके माप को प्रभावित करेगा। यह कई कारणों में से एक है कि क्यों System.nanoTimeउपयोगकर्ताओं को बेंचमार्किंग फ्रेमवर्क द्वारा अमूर्त किया जाना चाहिए

इसके अलावा, जेआईटी वार्मअप , कचरा संग्रहण, सिस्टम-वाइड इवेंट आदि जैसे विचार माप में अप्रत्याशितता का परिचय दे सकते हैं :

वार्मअप, डेड कोड एलिमिनेशन, फोर्किंग इत्यादि सहित टोंस के प्रभाव को कम करने की आवश्यकता है, सौभाग्य से, जेएमएच पहले से ही कई चीजों का ध्यान रखता है, और जावा और स्काला दोनों के लिए बाइंडिंग है।

ट्रेविस ब्राउन के जवाब के आधार पर यहाँ एक उदाहरण है कि कैसे स्काला के लिए जेएमएच बेंचमार्क सेटअप किया जाए

  1. Jmh को इसमें जोड़ें project/plugins.sbt
    addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7")
  2. में jmh plugin enable करें build.sbt
    enablePlugins(JmhPlugin)
  3. में जोड़े src/main/scala/bench/VectorAppendVsListPreppendAndReverse.scala

    package bench
    
    import org.openjdk.jmh.annotations._
    
    @State(Scope.Benchmark)
    @BenchmarkMode(Array(Mode.AverageTime))
    class VectorAppendVsListPreppendAndReverse {
      val size = 1_000_000
      val input = 1 to size
    
      @Benchmark def vectorAppend: Vector[Int] = 
        input.foldLeft(Vector.empty[Int])({ case (acc, next) => acc.appended(next)})
    
      @Benchmark def listPrependAndReverse: List[Int] = 
        input.foldLeft(List.empty[Int])({ case (acc, next) => acc.prepended(next)}).reverse
    }
  4. साथ बेंचमार्क निष्पादित करें
    sbt "jmh:run -i 10 -wi 10 -f 2 -t 1 bench.VectorAppendVsListPreppendAndReverse"

परिणाम हैं

Benchmark                                                   Mode  Cnt  Score   Error  Units
VectorAppendVsListPreppendAndReverse.listPrependAndReverse  avgt   20  0.024 ± 0.001   s/op
VectorAppendVsListPreppendAndReverse.vectorAppend           avgt   20  0.130 ± 0.003   s/op

ऐसा लगता है कि एक Listको फिर से प्रस्तुत करने का संकेत मिलता है और फिर इसे अंत में उलट एक आदेश में रखने की तुलना में तेजी से परिमाण का क्रम है Vector


1

दिग्गजों के कंधों पर खड़े होने के दौरान ...

एक ठोस 3-पार्टी लाइब्रेरी अधिक आदर्श होगी, लेकिन यदि आपको कुछ त्वरित और एसटीडी-लाइब्रेरी की आवश्यकता है, जो निम्नलिखित प्रकार प्रदान करती है:

  • repetitions
  • अंतिम परिणाम कई पुनरावृत्तियों के लिए जीतता है
  • कई पुनरावृत्तियों के लिए कुल समय और औसत समय
  • एक परम के रूप में समय / तत्काल प्रदाता की आवश्यकता को हटाता है

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> 

1

आप उपयोग कर सकते हैं 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.
}  

नैनो टाइम आपको दिखाएगा ns, इसलिए इसे देखना मुश्किल होगा। इसलिए मेरा सुझाव है कि आप इसके बजाय currentTimeMillis का उपयोग कर सकते हैं।


नैनोसेकंड को देखना मुश्किल है, दोनों के बीच चयन करने का एक खराब कारण है। संकल्प के अलावा कुछ महत्वपूर्ण अंतर हैं। एक के लिए, currentTimeMillis बदल सकता है और यहां तक ​​कि घड़ी के समायोजन के दौरान पीछे की ओर जा सकता है जो OS समय-समय पर करता है। एक और बात यह है कि नैनो टाइम थ्रेड सेफ नहीं हो सकता है: stackoverflow.com/questions/351565/…
क्रिस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.