-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Benchmarks and improve performance of ServletIo#requestBody
#171
Open
TimWSpence
wants to merge
10
commits into
http4s:series/0.23
Choose a base branch
from
TimWSpence:benchmarking
base: series/0.23
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 8 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
ca4b0cb
Benchmarks of reader vs requestBody
TimWSpence 2c3970d
Don't use concurrently with single-element stream
TimWSpence 7260d8d
Optimization: don't emit empty chunk
TimWSpence 831675b
Minimize allocations
TimWSpence 1179728
Suppress compiler warning
TimWSpence 290eb5b
Fix compiler warning
TimWSpence 9210893
Dispatcher per request in benchmark
rossabaker ffa5f3c
Bulk reads are more realistic
rossabaker 20467d6
Not sure how I broke the workflow, but alright
rossabaker 806fac7
Scalafmt
TimWSpence File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
183 changes: 183 additions & 0 deletions
183
benchmarks/src/main/scala/org/http4s/servlet/ServletIoBenchmarks.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
package org.http4s.servlet | ||
|
||
import cats.effect.IO | ||
import cats.effect.std.Dispatcher | ||
import cats.effect.unsafe.implicits.global | ||
|
||
import org.openjdk.jmh.annotations._ | ||
import org.http4s.servlet.NonBlockingServletIo | ||
|
||
import java.io.ByteArrayInputStream | ||
import java.util.concurrent.TimeUnit | ||
import javax.servlet.{ServletInputStream, ReadListener} | ||
import javax.servlet.http.HttpServletRequest | ||
import scala.util.Random | ||
|
||
/** To do comparative benchmarks between versions: | ||
* | ||
* benchmarks/run-benchmark AsyncBenchmark | ||
* | ||
* This will generate results in `benchmarks/results`. | ||
* | ||
* Or to run the benchmark from within sbt: | ||
* | ||
* Jmh / run -i 10 -wi 10 -f 2 -t 1 cats.effect.benchmarks.AsyncBenchmark | ||
* | ||
* Which means "10 iterations", "10 warm-up iterations", "2 forks", "1 thread". Please note that | ||
* benchmarks should be usually executed at least in 10 iterations (as a rule of thumb), but | ||
* more is better. | ||
*/ | ||
@State(Scope.Thread) | ||
@BenchmarkMode(Array(Mode.Throughput)) | ||
@OutputTimeUnit(TimeUnit.SECONDS) | ||
class ServletIoBenchmarks { | ||
|
||
@Param(Array("100000")) | ||
var size: Int = _ | ||
|
||
@Param(Array("1000")) | ||
var iters: Int = _ | ||
|
||
def servletRequest: HttpServletRequest = new HttpServletRequestStub( | ||
new TestServletInputStream(Random.nextBytes(size)) | ||
) | ||
|
||
@Benchmark | ||
def reader() = { | ||
val req = servletRequest | ||
val servletIo = NonBlockingServletIo[IO](4096) | ||
|
||
def loop(i: Int): IO[Unit] = | ||
if (i == iters) IO.unit else servletIo.reader(req).compile.drain >> loop(i + 1) | ||
|
||
loop(0).unsafeRunSync() | ||
} | ||
|
||
@Benchmark | ||
def requestBody() = { | ||
val req = servletRequest | ||
val servletIo = NonBlockingServletIo[IO](4096) | ||
|
||
def loop(i: Int): IO[Unit] = | ||
if (i == iters) IO.unit | ||
else Dispatcher.sequential[IO].use { dispatcher => | ||
servletIo.requestBody(req, dispatcher).compile.drain | ||
} >> loop(i + 1) | ||
|
||
loop(0).unsafeRunSync() | ||
} | ||
|
||
class TestServletInputStream(body: Array[Byte]) extends ServletInputStream { | ||
private var readListener: ReadListener = null | ||
private val in = new ByteArrayInputStream(body) | ||
|
||
override def isReady: Boolean = true | ||
|
||
override def isFinished: Boolean = in.available() == 0 | ||
|
||
override def setReadListener(readListener: ReadListener): Unit = { | ||
this.readListener = readListener | ||
readListener.onDataAvailable() | ||
} | ||
|
||
override def read(): Int = { | ||
val result = in.read() | ||
if (in.available() == 0) | ||
readListener.onAllDataRead() | ||
result | ||
} | ||
|
||
override def read(buf: Array[Byte]) = { | ||
val result = in.read(buf) | ||
if (in.available() == 0) | ||
readListener.onAllDataRead() | ||
result | ||
} | ||
|
||
override def read(buf: Array[Byte], off: Int, len: Int) = { | ||
val result = in.read(buf, off, len) | ||
if (in.available() == 0) | ||
readListener.onAllDataRead() | ||
result | ||
} | ||
} | ||
|
||
case class HttpServletRequestStub( | ||
inputStream: ServletInputStream | ||
) extends HttpServletRequest { | ||
def getInputStream(): ServletInputStream = inputStream | ||
|
||
def authenticate(x$1: javax.servlet.http.HttpServletResponse): Boolean = ??? | ||
def changeSessionId(): String = ??? | ||
def getAuthType(): String = ??? | ||
def getContextPath(): String = ??? | ||
def getCookies(): Array[javax.servlet.http.Cookie] = ??? | ||
def getDateHeader(x$1: String): Long = ??? | ||
def getHeader(x$1: String): String = ??? | ||
def getHeaderNames(): java.util.Enumeration[String] = ??? | ||
def getHeaders(x$1: String): java.util.Enumeration[String] = ??? | ||
def getIntHeader(x$1: String): Int = ??? | ||
def getMethod(): String = ??? | ||
def getPart(x$1: String): javax.servlet.http.Part = ??? | ||
def getParts(): java.util.Collection[javax.servlet.http.Part] = ??? | ||
def getPathInfo(): String = ??? | ||
def getPathTranslated(): String = ??? | ||
def getQueryString(): String = ??? | ||
def getRemoteUser(): String = ??? | ||
def getRequestURI(): String = ??? | ||
def getRequestURL(): StringBuffer = ??? | ||
def getRequestedSessionId(): String = ??? | ||
def getServletPath(): String = ??? | ||
def getSession(): javax.servlet.http.HttpSession = ??? | ||
def getSession(x$1: Boolean): javax.servlet.http.HttpSession = ??? | ||
def getUserPrincipal(): java.security.Principal = ??? | ||
def isRequestedSessionIdFromCookie(): Boolean = ??? | ||
def isRequestedSessionIdFromURL(): Boolean = ??? | ||
def isRequestedSessionIdFromUrl(): Boolean = ??? | ||
def isRequestedSessionIdValid(): Boolean = ??? | ||
def isUserInRole(x$1: String): Boolean = ??? | ||
def login(x$1: String, x$2: String): Unit = ??? | ||
def logout(): Unit = ??? | ||
def upgrade[T <: javax.servlet.http.HttpUpgradeHandler](x$1: Class[T]): T = ??? | ||
def getAsyncContext(): javax.servlet.AsyncContext = ??? | ||
def getAttribute(x$1: String): Object = ??? | ||
def getAttributeNames(): java.util.Enumeration[String] = ??? | ||
def getCharacterEncoding(): String = ??? | ||
def getContentLength(): Int = ??? | ||
def getContentLengthLong(): Long = ??? | ||
def getContentType(): String = ??? | ||
def getDispatcherType(): javax.servlet.DispatcherType = ??? | ||
def getLocalAddr(): String = ??? | ||
def getLocalName(): String = ??? | ||
def getLocalPort(): Int = ??? | ||
def getLocale(): java.util.Locale = ??? | ||
def getLocales(): java.util.Enumeration[java.util.Locale] = ??? | ||
def getParameter(x$1: String): String = ??? | ||
def getParameterMap(): java.util.Map[String, Array[String]] = ??? | ||
def getParameterNames(): java.util.Enumeration[String] = ??? | ||
def getParameterValues(x$1: String): Array[String] = ??? | ||
def getProtocol(): String = ??? | ||
def getReader(): java.io.BufferedReader = ??? | ||
def getRealPath(x$1: String): String = ??? | ||
def getRemoteAddr(): String = ??? | ||
def getRemoteHost(): String = ??? | ||
def getRemotePort(): Int = ??? | ||
def getRequestDispatcher(x$1: String): javax.servlet.RequestDispatcher = ??? | ||
def getScheme(): String = ??? | ||
def getServerName(): String = ??? | ||
def getServerPort(): Int = ??? | ||
def getServletContext(): javax.servlet.ServletContext = ??? | ||
def isAsyncStarted(): Boolean = ??? | ||
def isAsyncSupported(): Boolean = ??? | ||
def isSecure(): Boolean = ??? | ||
def removeAttribute(x$1: String): Unit = ??? | ||
def setAttribute(x$1: String, x$2: Object): Unit = ??? | ||
def setCharacterEncoding(x$1: String): Unit = ??? | ||
def startAsync( | ||
x$1: javax.servlet.ServletRequest, | ||
x$2: javax.servlet.ServletResponse, | ||
): javax.servlet.AsyncContext = ??? | ||
def startAsync(): javax.servlet.AsyncContext = ??? | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "4.2.4") | ||
addSbtPlugin("org.http4s" % "sbt-http4s-org" % "0.14.9") | ||
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦 I should have realized that the default is one byte at a time via
Read