วิธีการโพรไฟล์ใน Scala?


117

วิธีมาตรฐานในการเรียกเมธอด Scala คืออะไร

สิ่งที่ฉันต้องการคือขอเกี่ยวกับวิธีการซึ่งฉันสามารถใช้เพื่อเริ่มและหยุดตัวจับเวลา

ใน Java ฉันใช้การเขียนโปรแกรมด้านมุมมองด้าน J เพื่อกำหนดวิธีการที่จะทำโปรไฟล์และฉีดไบต์โค้ดเพื่อให้ได้สิ่งเดียวกัน

มีวิธีที่เป็นธรรมชาติมากขึ้นใน Scala ที่ฉันสามารถกำหนดฟังก์ชันมากมายที่จะเรียกก่อนและหลังฟังก์ชันโดยไม่สูญเสียการพิมพ์แบบคงที่ในกระบวนการหรือไม่


ถ้า 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 Kutschera


สิ่งที่คุณมีความสนใจมีอะไรบ้าง? คุณต้องการทราบว่าวิธีการบางอย่างใช้เวลานานแค่ไหนในสภาพแวดล้อมการผลิต จากนั้นคุณควรดูไลบรารีเมตริกและไม่ใช่การวัดแบบม้วนตัวเองเหมือนในคำตอบที่ยอมรับ หากคุณต้องการตรวจสอบว่ารหัสตัวแปรใดเร็วกว่า "โดยทั่วไป" กล่าวคือในสภาพแวดล้อมการพัฒนาของคุณให้ใช้ 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 }

เรียบร้อยแล้วฉันสามารถทำสิ่งเดียวกันโดยไม่ต้องเปลี่ยนรหัสได้หรือไม่?
sheki

ไม่ได้ใช้โซลูชันนี้โดยอัตโนมัติ Scala จะรู้ได้อย่างไรว่าคุณต้องการเวลาอะไร?
Jesper

1
นี่ไม่เป็นความจริงอย่างเคร่งครัด - คุณสามารถห่อสิ่งต่างๆใน REPL โดยอัตโนมัติ
oxbow_lakes

1
เกือบจะสมบูรณ์แบบ แต่คุณต้องตอบสนองต่อข้อยกเว้นที่เป็นไปได้เช่นกัน คำนวณt1ภายในfinallyข้อ
juanmirocks

2
คุณสามารถเพิ่มฉลากลงในภาพพิมพ์ของคุณด้วยการแกงบางอย่าง: def time[R](label: String)(block: => R): R = {จากนั้นเพิ่มฉลากในprintln
Glenn 'devalias'

34

นอกเหนือจากคำตอบของ Jesper แล้วคุณยังสามารถตัดการเรียกใช้เมธอดใน 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
เพื่อช่วยทุกคนที่มีปัญหาสงสัยตอนนี้:wrapคุณลักษณะนี้ถูกลบออกจาก REPL: - \
ches

25

มีไลบรารีเปรียบเทียบสามไลบรารีสำหรับ Scalaที่คุณสามารถใช้ประโยชน์ได้

เนื่องจาก URL บนไซต์ที่เชื่อมโยงมีแนวโน้มที่จะเปลี่ยนแปลงฉันจึงวางเนื้อหาที่เกี่ยวข้องด้านล่างนี้

  1. SPerformance - กรอบการทดสอบประสิทธิภาพที่มุ่งเป้าไปที่การเปรียบเทียบการทดสอบประสิทธิภาพและการทำงานใน Simple Build Tool โดยอัตโนมัติ

  2. scala-benchmarking-template - โครงการเทมเพลต SBT สำหรับการสร้างเกณฑ์มาตรฐาน Scala (micro-) ตาม Caliper

  3. เมตริก - การบันทึกเมตริกระดับ JVM และระดับแอปพลิเคชัน คุณก็รู้ว่าเกิดอะไรขึ้น


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
โปรดทราบว่า testing.Benchmark คือ @deprecated ("คลาสนี้จะถูกลบออก", "2.10.0")
Tvaroh

5

ฉันใช้วิธีแก้ปัญหาจาก 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

หวังว่านี่จะเป็นประโยชน์


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

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

3

ฉันชอบความเรียบง่ายของคำตอบของ @ 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 Hotspot วอร์มอัพลูป (ไม่ได้ตั้งเวลา) เพื่อกำหนดเวลาตัวอย่างขนาดเล็ก:

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
@nedim คำตอบสำหรับคำถาม - กระดาษห่อหุ้มสำหรับทุกสิ่งที่คุณต้องการเวลา ฟังก์ชันใด ๆ ที่ OP ต้องการเรียกสามารถวางไว้ในกระดาษห่อหุ้มหรือในบล็อกที่เรียกฟังก์ชันของเขาเพื่อให้เขา "สามารถกำหนดฟังก์ชันต่างๆที่จะเรียกก่อนและหลังฟังก์ชันได้โดยไม่สูญเสียการพิมพ์แบบคงที่"
Brent Faust

1
คุณพูดถูก ขออภัยฉันคงมองข้ามรหัสไปแล้ว เมื่อการแก้ไขของฉันได้รับการตรวจสอบฉันสามารถยกเลิกการลดคะแนนได้
nedim

3

แนวทางที่แนะนำในการเปรียบเทียบโค้ด Scala คือผ่านsbt-jmh

"ไม่ไว้วางใจใครบัลลังก์ทุกอย่าง" - ปลั๊กอิน sbt สำหรับ JMH (Java Microbenchmark Harness)

แนวทางนี้ถูกนำมาใช้โดยโครงการสกาลาที่สำคัญหลายโครงการเช่น

  • ภาษาโปรแกรมScalaนั่นเอง
  • Dotty (สกาลา 3)
  • ไลบรารีแมวสำหรับการเขียนโปรแกรมการทำงาน
  • เซิร์ฟเวอร์ภาษาโลหะสำหรับ IDE

จับเวลากระดาษห่อง่ายขึ้นอยู่กับSystem.nanoTimeเป็นไม่ได้เป็นวิธีการที่เชื่อถือของการเปรียบเทียบ:

System.nanoTimeแย่เหมือนString.internตอนนี้: คุณสามารถใช้ได้ แต่ใช้อย่างชาญฉลาด ผลของเวลาในการตอบสนองความละเอียดและความสามารถในการปรับขยายที่มาจากตัวจับเวลาอาจและจะส่งผลต่อการวัดของคุณหากทำโดยไม่เข้มงวดอย่างเหมาะสม นี่เป็นหนึ่งในหลาย ๆ เหตุผล System.nanoTimeที่ผู้ใช้ควรสรุปจากกรอบการเปรียบเทียบ

นอกจากนี้ข้อควรพิจารณาเช่นการอุ่นเครื่อง JITการรวบรวมขยะเหตุการณ์ทั้งระบบ ฯลฯ อาจทำให้เกิดความไม่สามารถคาดเดาได้ในการวัด:

ต้องลดเอฟเฟกต์มากมายรวมถึงการวอร์มอัพการกำจัดโค้ดที่ตายแล้วการฟอร์ก ฯลฯ โชคดีที่ JMH ดูแลหลาย ๆ อย่างอยู่แล้วและมีการเชื่อมโยงสำหรับทั้ง Java และ Scala

จากคำตอบของ Travis Brownนี่คือตัวอย่างวิธีการตั้งค่ามาตรฐาน JMH สำหรับ Scala

  1. เพิ่ม jmh ไปที่ project/plugins.sbt
    addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7")
  2. เปิดใช้งานปลั๊กอิน jmh 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

ซึ่งดูเหมือนจะบ่งบอก prepending ไปแล้วย้อนกลับในตอนท้ายเป็นลำดับความสำคัญได้เร็วกว่าเก็บผนวกกับListVector


1

ขณะยืนอยู่บนไหล่ของยักษ์ ...

ไลบรารีของบุคคลที่สามที่มั่นคงจะเหมาะกว่า แต่ถ้าคุณต้องการบางสิ่งที่รวดเร็วและใช้ไลบรารีมาตรฐานตัวแปรต่อไปนี้จะให้:

  • ซ้ำ
  • ผลสุดท้ายชนะสำหรับการทำซ้ำหลายครั้ง
  • เวลารวมและเวลาเฉลี่ยสำหรับการทำซ้ำหลายครั้ง
  • ไม่ต้องใช้เวลา / ผู้ให้บริการทันทีเป็นพารามิเตอร์

.

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.
}  

nanoTime จะแสดงให้คุณเห็นnsดังนั้นจึงยากที่จะเห็น ดังนั้นฉันขอแนะนำว่าคุณสามารถใช้ currentTimeMillis แทนได้


นาโนวินาทีที่มองเห็นได้ยากเป็นเหตุผลที่ไม่ดีในการเลือกระหว่างทั้งสอง มีความแตกต่างที่สำคัญบางอย่างนอกเหนือจากความละเอียด ประการแรก currentTimeMillis สามารถเปลี่ยนและย้อนกลับได้ในระหว่างการปรับนาฬิกาที่ระบบปฏิบัติการทำงานเป็นระยะ ๆ อีกประการหนึ่งคือ nanoTime อาจไม่ปลอดภัยต่อเธรด: stackoverflow.com/questions/351565/…
คริส
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.