All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalameter.execution.SeparateJvmsExecutor.scala Maven / Gradle / Ivy

The newest version!
package org.scalameter
package execution



import org.scalameter.picklers.Pickler
import scala.collection.{mutable, Seq}
import scala.util.Try
import org.scalameter.PrettyPrinter.Implicits._
import org.scalameter.utils.Tree



/** Runs multiple JVM instances per each setup and aggregates all the results together.
 *
 *  This produces more stable results, as the performance related effects of each JVM
 *  instantiation are averaged.
 */
class SeparateJvmsExecutor[V: Pickler : PrettyPrinter](
  val warmer: Warmer,
  val aggregator: Aggregator[V],
  val measurer: Measurer[V]
) extends Executor[V] {
  import Key._

  val runner = new JvmRunner

  def createJvmContext(ctx: Context) = {
    ctx ++ Seq(
      exec.overallBegin -> currentContext(exec.overallBegin)
    )
  }

  override def run[T](
    setuptree: Tree[Setup[T]],
    reporter: Reporter[V],
    persistor: Persistor
  ): Tree[CurveData[V]] = {
    var count = 0
    for (setup <- setuptree) {
      count += 1
    }
    var result: Tree[CurveData[V]] = null
    val newContext = currentContext ++ Seq(
      exec.setupCount -> count,
      exec.setupIndex -> 0
    )
    for (_ <- dyn.currentContext.using(newContext)) {
      result = super.run(setuptree, reporter, persistor)
    }
    log.clear()
    result
  }

  def runSetup[T](setup: Setup[T]): CurveData[V] = {
    import setup._
    import SeparateJvmsExecutor.computeOverallProgress

    val warmups = context(exec.maxWarmupRuns)
    val totalreps = context(exec.benchRuns)
    val independentSamples = context(exec.independentSamples)

    def repetitions(idx: Int): Int = {
      val is = independentSamples
      totalreps / is + (if (idx < totalreps % is) 1 else 0)
    }

    val m = measurer
    val w = warmer
    val jvmContext = createJvmContext(context)

    log.currentTotalForks(independentSamples)

    def sample(idx: Int, reps: Int): Try[Seq[(Parameters, Seq[(V, String)])]] = {
      val forkCommand = runner.commandFor(jvmContext).mkString(" ")
      val setupIndex = currentContext(exec.setupIndex)
      val setupCount = currentContext(exec.setupCount)
      runner.run(jvmContext) {
        dyn.currentContext.value = jvmContext
        val progress = computeOverallProgress(
          idx, independentSamples, setupIndex, setupCount, 0.0)
        log.overallBegin(jvmContext(exec.overallBegin))
        log.overallScope(jvmContext.scope + " \ud83e\udc7a " + jvmContext(dsl.curve))
        log.overallProgress(progress)
        log.currentForkIndex(idx)
        log.currentTotalForks(independentSamples)
        log.currentForkCommand(forkCommand)

        log.info(
          s"Sampling $reps measurements in separate JVM invocation $idx - " +
            s"${jvmContext.scope}, ${jvmContext(dsl.curve)}."
        )

        // warmup
        setupBeforeAll()
        try {
          log.timer(true)
          customwarmup match {
            case Some(warmup) =>
              log.currentInput("Running custom warmup ...")
              for (i <- 0 until warmups) warmup()
            case _ =>
              log.currentInput("Running warmup ...")
              for (x <- gen.warmupset) {
                for (i <- w.warming(jvmContext, setupFor(x), teardownFor(x))) snippet(x)
              }
          }
          log.currentProgress(10.0)

          // perform GC
          compat.Platform.collectGarbage()

          // measure
          val observations = new mutable.ArrayBuffer[(Parameters, Seq[(V, String)])]()
          val totalDatasets = gen.cardinality
          var datasetIndex = 0
          for (params <- gen.dataset) {
            datasetIndex += 1
            val set = setupFor()
            val tear = teardownFor()
            val regen = regenerateFor(params)
            val currentProgress = 10.0 + 80.0 * datasetIndex / totalDatasets
            val overallProgress = computeOverallProgress(
              idx, independentSamples, setupIndex, setupCount, currentProgress)
            log.currentInput(params.toString)
            log.currentProgress(currentProgress)
            log.overallProgress(overallProgress)
            val results = m.measure(context, reps, set, tear, regen, snippet)
            observations += ((params, results.map(q => q.value -> q.units)))
            // FIXME: `java.lang.ClassNotFoundException: org.scalameter.Quantity`
          }
          observations
        } finally {
          teardownAfterAll()
          log.timer(false)
          log.clear()
        }
      }
    }

    def sampleReport(idx: Int, reps: Int): Seq[(Parameters, Seq[Quantity[V]])] = try {
      sample(idx, reps).get.map(v => (v._1, v._2.map(v => Quantity(v._1, v._2))))
    } catch {
      case t: Throwable =>
        log.error(s"Error running separate JVM: $t")
        log.error(s"Classpath: ${sys.props("java.class.path")}")
        throw t
    }

    log.verbose(s"Running test set for ${context.scope}, curve ${context(dsl.curve)}")
    log.verbose(
      s"Starting $totalreps measurements across " +
        s"$independentSamples independent JVM runs."
    )

    val valueseqs = for {
      idx <- 0 until independentSamples
      reps = repetitions(idx)
    } yield {
      val setupIndex = currentContext(exec.setupIndex)
      val setupCount = currentContext(exec.setupCount)
      log.currentForkIndex(idx)
      log.overallProgress(
        computeOverallProgress(idx, independentSamples, setupIndex, setupCount, 0.0))
      sampleReport(idx, reps)
    }

    val valueseq = valueseqs.reduceLeft { (accseq, vs) =>
      accseq zip vs map {
        case ((k1, x), (k2, y)) => (k1, x ++ y)
      }
    }

    def nice(vs: Seq[(Parameters, Seq[Quantity[V]])]) = vs map { case (params, seq) =>
      params.axisData.mkString(", ") + ": " +
        seq.map(t => s"${t.value.prettyPrint}").mkString(", ")
    } mkString ("\n")

    log.verbose(s"Obtained measurements:\n${nice(valueseq)}")

    val measurements = valueseq map {
      case (params, values) =>
        val single = aggregator(values)
        Measurement(
          single.value,
          params,
          aggregator.data(values),
          single.units
        )
    }

    dyn.currentContext.value = currentContext ++ Seq(
      exec.setupIndex -> (currentContext(exec.setupIndex) + 1)
    )

    CurveData(measurements.toSeq, Map.empty, context)
  }

  override def toString =
    s"SeparateJvmsExecutor(${aggregator.name}, ${measurer.name})"

}


object SeparateJvmsExecutor extends Executor.Factory[SeparateJvmsExecutor] {

  def apply[T: Pickler : PrettyPrinter](w: Warmer, agg: Aggregator[T], m: Measurer[T]) =
    new SeparateJvmsExecutor(w, agg, m)

  private[execution] def computeOverallProgress(
    forkIndex: Int, totalForks: Int, setupIndex: Int, setupCount: Int, currentProgress: Double
  ): Double = {
    val fraction = math.max(0.0, math.min(1.0, currentProgress / 100.0))
    (setupIndex + (forkIndex + fraction).toDouble / totalForks) / setupCount * 100
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy