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

com.twitter.concurrent.ConduitSpscBenchmark.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.concurrent

import com.twitter.io.{Buf, Reader}
import com.twitter.util.{Await, Future, StdBenchAnnotations}
import org.openjdk.jmh.annotations.{Benchmark, Param, Scope, State}

/**
 * These benchmarks measure the overhead of various implementations of a
 * conduit between a `source: () => Future[Buf]` and `sink: Buf =>
 * Future[Unit]`.
 *
 * The steps are as follows:
 *
 * 1. connect the source to the conduit
 * 2. drain three items from the conduit into the sink
 *
 * The control benchmark measures the baseline when there is no conduit, i.e.,
 * the source feeds directly into the sink.
 */
@State(Scope.Benchmark)
class ConduitSpscBenchmark extends StdBenchAnnotations {
  @Param(Array("1", "2", "5", "10"))
  var size: Int = _

  private[this] val buf = Buf.Utf8("howdy")

  private[this] val b = new Broker[Buf]
  private[this] val q = new AsyncQueue[Buf]
  private[this] val r = Reader.writable()

  private[this] def sink(buf: Any): Future[Boolean] = Future.True
  private[this] def source(): Future[Buf] = Future.value(buf)

  private[this] def runControl(n: Int): Future[Boolean] =
    if (n <= 1) source().flatMap(sink)
    else source().flatMap(sink).flatMap(_ => runControl(n - 1))

  /**
   * The control benchmark.
   *
   * `source` is plugged directly into `sink` without a conduit.
   */
  @Benchmark
  def control: Boolean =
    Await.result(runControl(size))

  private[this] def feedQueue(n: Int): Future[Unit] =
    if (n <= 0) Future.Done
    else source().map(q.offer).flatMap(_ => feedQueue(n - 1))

  private[this] def consumeQueue(n: Int): Future[Boolean] =
    if (n <= 1) q.poll().flatMap(sink)
    else q.poll().flatMap(sink).flatMap(_ => consumeQueue(n - 1))

  @Benchmark
  def asyncQueue: Boolean = Await.result({
    // When the producer outpaces the consumer, AsyncQueue are buffers writes,
    // so it's impossible for the producer to rendezvous with the reader. The
    // offers aren't coordinated with the polls. As a result, the AsyncQueue
    // has a little speed advantage.

    // Produce
    feedQueue(size)

    // Consume
    consumeQueue(size)
  })

  private[this] def mkAsyncStream(n: Int): AsyncStream[Buf] =
    if (n <= 0) AsyncStream.empty
    else if (n == 1) AsyncStream.fromFuture(source())
    else AsyncStream.fromFuture(source()) ++ mkAsyncStream(n - 1)

  @Benchmark
  def asyncStream: Boolean = Await.result({
    // AsyncStream is a persistent structure, so access is effectively
    // memoized, which means we have to create one new for each benchmark,
    // otherwise it'd be cheating.

    // Produce
    val stream = mkAsyncStream(size)

    // Consume
    stream.foldLeftF(false) { (_, buf) => sink(buf) }
  })

  private[this] def feedBroker(n: Int): Future[Unit] =
    if (n <= 0) Future.Done
    else source().flatMap(b.send(_).sync()) before feedBroker(n - 1)

  private[this] def consumeBroker(n: Int): Future[Boolean] =
    if (n <= 1) b.recv.sync().flatMap(sink)
    else b.recv.sync().flatMap(sink).flatMap(_ => consumeBroker(n - 1))

  @Benchmark
  def broker: Boolean = Await.result({
    // Produce
    feedBroker(size)

    // Consume
    consumeBroker(size)
  })

  private[this] def feedReader(n: Int): Future[Unit] =
    if (n <= 0) Future.Done
    else source().flatMap(r.write) before feedReader(n - 1)

  private[this] def consumeReader(n: Int): Future[Boolean] =
    if (n <= 1) r.read(Int.MaxValue).flatMap(sink)
    else r.read(Int.MaxValue).flatMap(sink).flatMap(_ => consumeReader(n - 1))

  @Benchmark
  def reader: Boolean = Await.result({
    // Produce
    feedReader(size)

    // Consume
    consumeReader(size)
  })

  private[this] def mkSpool(n: Int): Future[Spool[Buf]] =
    if (n <= 0) Future.value(Spool.empty)
    else source().map(_ *:: mkSpool(n - 1))

  private[this] def consumeSpool[A](spool: Spool[A], b: Boolean): Future[Boolean] =
    if (spool.isEmpty) Future.value(b)
    else sink(spool.head).flatMap { newB =>
      spool.tail.flatMap { tail =>
        consumeSpool(tail, newB)
      }
    }

  @Benchmark
  def spool: Boolean = Await.result({
    // Produce
    val f = mkSpool(size)

    // Consume
    f.flatMap { s => consumeSpool(s, false) }
  })
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy