Permalink
Browse files

front loading

  • Loading branch information...
sirocchj committed Jun 5, 2018
0 parents commit 2aac64da0c69eb14ca9b42523c07f001f2eada55
Showing with 8,035 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +3 −0 .sbtopts
  3. +2 −0 .scalafmt.conf
  4. +32 −0 .travis.yml
  5. BIN .travis/secrets.tar.enc
  6. +21 −0 LICENSE
  7. +116 −0 README.md
  8. +166 −0 benchmarks/core/README.md
  9. +706 −0 benchmarks/core/flamegraphs/baseline-string-encode-flame-graph-cpu-20180508.svg
  10. +1,018 −0 benchmarks/core/flamegraphs/bulk-string-encode-flame-graph-cpu-20180508.svg
  11. +1,226 −0 benchmarks/core/flamegraphs/simple-string-encode-flame-graph-cpu-20180508.svg
  12. +19 −0 benchmarks/core/src/main/scala/laserdisc/protocol/ProtocolBench.scala
  13. +52 −0 benchmarks/core/src/main/scala/laserdisc/protocol/RESPBench.scala
  14. +14 −0 benchmarks/core/src/main/scala/laserdisc/protocol/RESPParamWriteBench.scala
  15. +45 −0 benchmarks/core/src/main/scala/laserdisc/protocol/UTF8EncodingBench.scala
  16. +246 −0 build.sbt
  17. +5 −0 core/.js/src/main/scala/laserdisc/Platform.scala
  18. +5 −0 core/.jvm/src/main/scala/laserdisc/Platform.scala
  19. +34 −0 core/src/main/boilerplate/BListPExtra.scala.template
  20. +32 −0 core/src/main/boilerplate/HashPExtra.scala.template
  21. +19 −0 core/src/main/boilerplate/HyperLogLogPExtra.scala.template
  22. +24 −0 core/src/main/boilerplate/KeyPExtra.scala.template
  23. +16 −0 core/src/main/boilerplate/ListPExtra.scala.template
  24. +53 −0 core/src/main/boilerplate/SetPExtra.scala.template
  25. +72 −0 core/src/main/boilerplate/SortedSetPExtra.scala.template
  26. +28 −0 core/src/main/boilerplate/StringPExtra.scala.template
  27. +14 −0 core/src/main/scala/laserdisc/auto.scala
  28. +116 −0 core/src/main/scala/laserdisc/package.scala
  29. +31 −0 core/src/main/scala/laserdisc/protocol/BListP.scala
  30. +25 −0 core/src/main/scala/laserdisc/protocol/ConnectionP.scala
  31. +81 −0 core/src/main/scala/laserdisc/protocol/HashP.scala
  32. +15 −0 core/src/main/scala/laserdisc/protocol/HyperLogLogP.scala
  33. +190 −0 core/src/main/scala/laserdisc/protocol/KeyP.scala
  34. +14 −0 core/src/main/scala/laserdisc/protocol/LenientStringCodec.scala
  35. +78 −0 core/src/main/scala/laserdisc/protocol/ListP.scala
  36. +88 −0 core/src/main/scala/laserdisc/protocol/Protocol.scala
  37. +11 −0 core/src/main/scala/laserdisc/protocol/PublishP.scala
  38. +362 −0 core/src/main/scala/laserdisc/protocol/RESP.scala
  39. +59 −0 core/src/main/scala/laserdisc/protocol/RESPParamWrite.scala
  40. +67 −0 core/src/main/scala/laserdisc/protocol/RESPRead.scala
  41. +280 −0 core/src/main/scala/laserdisc/protocol/Read.scala
  42. +242 −0 core/src/main/scala/laserdisc/protocol/ServerP.scala
  43. +84 −0 core/src/main/scala/laserdisc/protocol/SetP.scala
  44. +62 −0 core/src/main/scala/laserdisc/protocol/Show.scala
  45. +341 −0 core/src/main/scala/laserdisc/protocol/SortedSetP.scala
  46. +217 −0 core/src/main/scala/laserdisc/protocol/StringP.scala
  47. +26 −0 core/src/main/scala/laserdisc/syntax.scala
  48. +8 −0 core/src/main/scala/laserdisc/types.scala
  49. +253 −0 core/src/test/scala-2.11/laserdisc/HashPSpec.scala
  50. +253 −0 core/src/test/scala-2.12/laserdisc/HashPSpec.scala
  51. +145 −0 core/src/test/scala/laserdisc/BListPSpec.scala
  52. +126 −0 core/src/test/scala/laserdisc/ConnectionPSpec.scala
  53. +61 −0 core/src/test/scala/laserdisc/HyperLogLogPSpec.scala
  54. +175 −0 core/src/test/scala/laserdisc/protocol/RESPCodecsSpec.scala
  55. +14 −0 fs2/src/main/scala-2.11/laserdisc/fs2/fs2.scala
  56. +5 −0 fs2/src/main/scala-2.12/laserdisc/fs2/fs2.scala
  57. +108 −0 fs2/src/main/scala/laserdisc/fs2/CLI.scala
  58. +62 −0 fs2/src/main/scala/laserdisc/fs2/Logger.scala
  59. +28 −0 fs2/src/main/scala/laserdisc/fs2/PromiseMapper.scala
  60. +43 −0 fs2/src/main/scala/laserdisc/fs2/ProtocolHandler.scala
  61. +13 −0 fs2/src/main/scala/laserdisc/fs2/RedisAddress.scala
  62. +277 −0 fs2/src/main/scala/laserdisc/fs2/RedisClient.scala
  63. +63 −0 fs2/src/main/scala/laserdisc/fs2/RedisConnection.scala
  64. +19 −0 fs2/src/main/scala/laserdisc/fs2/Request.scala
  65. +11 −0 fs2/src/main/scala/laserdisc/fs2/exceptions.scala
  66. +1 −0 project/build.properties
  67. +8 −0 project/plugins.sbt
  68. +1 −0 project/project/plugins.sbt
@@ -0,0 +1,4 @@
**/target/
.idea
*.DS_Store
local.*
@@ -0,0 +1,3 @@
-J-Xms2g
-J-Xmx4g
-J-XX:MaxMetaspaceSize=512m
@@ -0,0 +1,2 @@
align = true # For pretty alignment.
maxColumn = 120 # For my wide 30" display.
@@ -0,0 +1,32 @@
sudo: false
language: scala
scala:
- 2.12.4-bin-typelevel-4
- 2.11.11-bin-typelevel-4
jdk:
- oraclejdk8
addons:
apt:
packages:
- oracle-java8-installer
script:
- sbt -Dfile.encoding=UTF8 test
after_success:
- if [ $TRAVIS_PULL_REQUEST = 'false' ]; then sbt ++$TRAVIS_SCALA_VERSION releaseEarly; fi
before_cache:
- find $HOME/.sbt -name "*.lock" | xargs rm
- find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm
cache:
directories:
- "$HOME/.coursier"
- "$HOME/.ivy2/cache"
- "$HOME/.sbt/boot/"
before_install:
- if [ $TRAVIS_PULL_REQUEST = 'false' ]; then openssl aes-256-cbc -K $encrypted_d57b5eec735f_key
-iv $encrypted_d57b5eec735f_iv -in .travis/secrets.tar.enc -out .travis/local.secrets.tar
-d; tar xv -C .travis -f .travis/local.secrets.tar; fi
env:
global:
- secure: kpqVLCzOiwKwRRCHJGMIucMMt+beI8Uhaqp9jYV3gYbGZi86JzkMemVh9Y29mSYz8zkqEtlmEF8FOPrMmz56WvnRm2x3yTaFySMNSGf5Rf+0NWejU/Cs1JOc0bonxBhf+Y8cP3yqNBAcsoatrLsIwZECRax9p0TxMn6+MICjrhtY4RDn9WEdatdpjgsJEzYi2dz8Z2FpM8dFRqBWB2oAOBTW6TvjRPqNJ2+xHH3t7lPyub/WPfug1GDUw9g7roRQuwU7Bq+9OuCozdAs8sbt/z0TqdWz5+d0qe9/gYCn3bp4g7lVB753fRy3a0aOTVFMg0y6SV60u85k3RYx9UpysgAkbqkqixBIVXQL9o4DAKDIeqIQQxWUvL28UpLJ0D6hWmpzyuQIOaK9KmGLOpsHzMxugs1uXHWWphSRhsY9tlHfKmNe3UKPsmD3OkvhFdPOsAM1lWF4U9s0GnVpgsBetbyJhMPCTUHAiPnOvWyjK2wShENYAEFzhCvrt3k2z5qzEA1BWx6HJY2/+Ra2d1FugTKBeSghYC3tOE+Y54eof9xyVEc/AnAIw6uX6GAt3kSCoujETdDg4TWpA+200dNv9jFVHjy4Bjxvulk4hsAblMfwjELYAdOf0NjLDOKzuR3S8/XS5mG1btgiMLUbYkgu4AyPJIKmdKvY5R4ZPJ+QtU4=
- secure: UCmJ/R5ShE97hYIvsft4/Yh4B8gFKg2dJHvhlRJqb2VMWYFZZNRXiiFmpKUaaqWt0Wh3wstHKXIhTGHroqV9spnvd60EAFSeLZOp4ZaJx1F6ZM8af6jRyB5MHHaQb+50pjzq3ikkkP+fJ97+Ee8tN+o73M+CAHxYngJdzihXtpWNTaCyFHQMu2V38II1MAzvy3TeZt6EypTARQK6z4ZmipcGNF1Himb4cOsH1opoSRk+tFlTNO7lFCg9iz4mdCWspxMA6PCCQ575MouBv4gbwjCUPW825MazPSfEeMkwUfq5gxVCjFkYQIw+nZ3ZXtYs4N7E5HKS+VRUBAZNvs+mWFmGOfE5cHAI3fvUTyEyTMQUWVGjZ/5UHVVcrx1xbSWdTUczfwQvkTDcojTtQ6TOoo9kWJDJe2B/Oroxxgu7SirUNPOnBuC1ZhM3TtxI1Gzow4nHRYidD2kT5qOX/IRLtS4K2Iy2502X+Dn+6edLxb76v+ONOJ2NQd9G7tRsKbM/VIAby2/zQDeZetJ7NzQJLGj5ajagCvidzMiSq4w5PsAfRsUrhVtrhZ3zTKWHO87rShSz5oTMWAS0+RLZ0ra4YcEi8OnM1FUBf/nvZdiJu8X01UlGPHNXp0PkhPN3a1KT0sEPAm0OxTgo78+zosbuxKC+IswuWMeKsDkSRJDIbWQ=
- secure: dQfZfrC73Oyj+6jEpCpMWIIUsr6Wm7WBLREw1X+nSxA3/ZkIqZ25xIs4kN/o4gv2ay43hwLpDKfeP4dnpSYt7mPu5T2wlP+5Ih1zrZFXJrUD5RkuRzeKHG3S4aKEJcLK85dYCdKLhtJXeaBOeOUTrUcrEXPlbTTW91uWU5ewuTsjmI0TDPesi5YvCB2V0tihyMBmnXzxwZHKnJYVfdGQhPKyMB5JetIW7prvQoJtsK4ViIQJjunlY9wB9ENu92mXYQ8SbMo1gCjT3+XDcrYRgTh56uKx9hzp1o1RvColW2+kWM3OEWScU2Z/GPzDvn37i3e6DVP3SOhS/mAW3I8FyeRpGSRlPbB2RJ+qyc9gBWYgh2L3Y5sNVi7UrOYOjOAA/KWa8XYqVg+iTYjpnzIt1Qj7NdhjVuDpe4MJhz20wNXPyffqsNSrRrc524GebymyKK1Yll1nHDQNfpOaHoWudt4s8AIAkMJFPcGOi5WWZvaW5jQWJrTuX1cMvtx4nX85jhm8piEC0PVhxoB3GaxVWktrB6QVXFueFM7YYhu8PZxOshOFVmPcwD6E3hmspuNB2IIbdBPYlYrLl07hOq+I/+J94Hi+8asnPvujFmKH0lnJZiC0pwfzLAXV+E1b3DcxDw7Uf/x/8tKa0LOVYLYYbI3PRpBvTTrCNKXSq/0asTQ=
Binary file not shown.
21 LICENSE
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Julien Sirocchi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
116 README.md
@@ -0,0 +1,116 @@
## LaserDisc
[![Build Status](https://travis-ci.org/laserdisc-io/laserdisc.svg?branch=master)](https://travis-ci.org/laserdisc-io/laserdisc)
[![Latest version](https://index.scala-lang.org/laserdisc/laserdisc/laserdisc-core/latest.svg?color=orange&v=1)](https://index.scala-lang.org/laserdisc/laserdisc/laserdisc-core)
[![Latest version](https://index.scala-lang.org/laserdisc/laserdisc/laserdisc-fs2/latest.svg?color=blue&v=1)](https://index.scala-lang.org/laserdisc/laserdisc/laserdisc-fs2)
[![Scala.js](http://scala-js.org/assets/badges/scalajs-0.6.17.svg)](http://scala-js.org)
LaserDisc is a(nother) Scala driver for [Redis](https://redis.io/), written in Scala from the ground up.
It differentiates itself from the others for having a core layer, which is made up of all the supported Redis commands
and the Redis Serialization Protocol ([RESP](https://redis.io/topics/protocol)), that is strongly typed and which makes
heavy use of [shapeless](https://github.com/milessabin/shapeless) and [refined](https://github.com/fthomas/refined) to
achieve this. It's also worth noting that the core - in order to be built - makes use of
[Typelevel's Scala 2.12](https://typelevel.org/scala) fork, since it requires the enhancements on implicit heuristics
and the surfacing of literal types. Finally, it also provides an implementation of RESP built using
[scodec](http://scodec.org/).
On top of this, one or more clients can be implemented. The only one currently available out of the box is built using
[fs2](https://functional-streams-for-scala.github.io/fs2/)/[cats effect](https://typelevel.org/cats-effect/) but
more competing implementations can be added with limited effort. This implementation has found great inspiration from
the excellent [fs2-kafka](https://github.com/Spinoco/fs2-kafka/) library.
### *Disclaimer*
:warning: Development is very much early stages and subject to frequent and breaking changes. :warning:
What's there:
- [x] Codecs for Redis' RESP wire format
- [x] Fully-fledged protocol encapsulating request/response pairs for (almost) all Redis [commands](https://redis.io/commands)
- [x] Initial version of single-node Redis client. Lots of improvements needed
- [x] Minimal CLI
What's missing:
- [ ] Everything else :)
### Why the name
Two reasons:
1. "A LaserDisc" is an anagram for "Scala Redis"
2. LaserDiscs were invented in 1978 (same year I was born) and were so cool (and foundational, more on [Wikipedia](https://en.wikipedia.org/wiki/LaserDisc))
### Getting Started
LaserDisc is currently available for Scala 2.11 and 2.12 on the JVM.
Its core (protocol commands and RESP wire format) is also available for [Scala.JS](http://www.scala-js.org/).
To add LaserDisc as a dependency to your project just add the following to your `build.sbt`:
```
libraryDependencies += "io.laserdisc" %% "laserdisc-fs2" % latestVersion
```
If you only need protocols (i.e. Redis commands and RESP wire format), you may simply add:
```
libraryDependencies += "io.laserdisc" %% "laserdisc-core" % latestVersion
```
### Example usage
With a running Redis instance on `localhost:6379` try running the following:
```scala
import java.nio.channels.AsynchronousChannelGroup
import java.util.concurrent.Executors.newFixedThreadPool
import cats.effect.IO
import cats.syntax.apply._
import fs2.{Scheduler, Stream, StreamApp}
import laserdisc._
import laserdisc.auto._
import laserdisc.fs2._
import scala.concurrent.ExecutionContext
object Main extends StreamApp[IO] {
implicit final val ec: ExecutionContext = ExecutionContext.global
implicit final val asg: AsynchronousChannelGroup = AsynchronousChannelGroup.withThreadPool(newFixedThreadPool(2))
implicit final val logger: Logger[IO] = new Logger[IO] {
override def log(level: Logger.Level, msg: => String, maybeT: Logger.MaybeT): IO[Unit] = IO {
println(s">>> $msg")
}
}
override final def stream(args: List[String], requestShutdown: IO[Unit]): Stream[IO, StreamApp.ExitCode] =
Scheduler[IO](corePoolSize = 1).flatMap { implicit scheduler =>
RedisClient[IO](Set(RedisAddress("localhost", 6379))).evalMap { client =>
client.send2(string.set("a", 23), string.get[PosInt]("a")).flatMap {
case (Right("OK"), Right(Some(getResponse))) if getResponse.value == 23 => logger.info("yay!") *> IO.pure(StreamApp.ExitCode.Success)
case other => logger.error(s"something went terribly wrong $other") *> IO.raiseError(new RuntimeException("boom"))
}
}
}
.onFinalize(IO(asg.shutdown))
}
```
This should produce the following output:
```bash
>>> Starting connection
>>> Server available for publishing: localhost:6379
>>> sending Array(BulkString(SET),BulkString(a),BulkString(23))
>>> receiving SimpleString(OK)
>>> sending Array(BulkString(GET),BulkString(a))
>>> receiving BulkString(23)
>>> yay!
>>> Shutting down connection
>>> Connection terminated: Left(java.nio.channels.ShutdownChannelGroupException)
```
## License
LaserDisc is licensed under the **[MIT License](LICENSE)** (the "License"); you may not use this software except in
compliance with the License.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
@@ -0,0 +1,166 @@
## More on charsets... _(11 May 2018)_
Added a benchmark to pinpoint differences in pure raw performance between `StandardCharsets.UTF_8.encode(s:String): ByteBuffer`
(used by `scodec.codecs.utf8`) and `(s: String).getBytes(StandardCharsets.UTF_8): Array[Byte]`.
Also interesting to see the reciprocal, i.e. `(StandardCharsets.UTF_8.decode(bb: ByteBuffer): CharBuffer).toString`
vs `new String(b: Array[Byte], StandardCharsets.UTF_8)`.
Also, implemented [LenientStringCodec](https://github.com/laserdisc-io/laserdisc/tree/master/protocol/src/main/scala/laserdisc/protocol/LenientStringCodec.scala)
to prove whether handling UTF8 encoding/decoding as the former of the two (i.e. lenient w.r.t. malformed/invalid data
_and_ leveraging underlying `System.arrayCopy` always) would lead to overall better performance.
```bash
sbt:laserdisc> cd protocol-benchmarks
sbt:protocol-bench> jmh:run -i 20 -wi 10 -f2 -t1 .*UTF8.*
[info] Benchmark Mode Cnt Score Error Units
[info] UTF8EncodingBench.decode_long_string_baseline_charset thrpt 40 681609.456 ± 4618.627 ops/s
[info] UTF8EncodingBench.decode_long_string_baseline_string thrpt 40 1014156.061 ± 6055.819 ops/s
[info] UTF8EncodingBench.decode_long_string_default_utf8 thrpt 40 588797.370 ± 2722.294 ops/s
[info] UTF8EncodingBench.decode_long_string_lenient_utf8 thrpt 40 816049.009 ± 4034.696 ops/s
[info] UTF8EncodingBench.decode_ok_baseline_charset thrpt 40 17878620.315 ± 120801.187 ops/s
[info] UTF8EncodingBench.decode_ok_baseline_string thrpt 40 29110641.894 ± 226674.439 ops/s
[info] UTF8EncodingBench.decode_ok_default_utf8 thrpt 40 10759072.941 ± 113434.374 ops/s
[info] UTF8EncodingBench.decode_ok_lenient_utf8 thrpt 40 12396142.937 ± 75330.784 ops/s
[info] UTF8EncodingBench.encode_long_string_baseline_charset thrpt 40 228850.537 ± 994.737 ops/s
[info] UTF8EncodingBench.encode_long_string_baseline_string thrpt 40 830896.958 ± 5961.262 ops/s
[info] UTF8EncodingBench.encode_long_string_default_utf8 thrpt 40 217622.095 ± 1312.185 ops/s
[info] UTF8EncodingBench.encode_long_string_lenient_utf8 thrpt 40 818939.332 ± 4016.609 ops/s
[info] UTF8EncodingBench.encode_ok_baseline_charset thrpt 40 21983610.584 ± 189013.347 ops/s
[info] UTF8EncodingBench.encode_ok_baseline_string thrpt 40 23865417.974 ± 170500.956 ops/s
[info] UTF8EncodingBench.encode_ok_default_utf8 thrpt 40 13551775.934 ± 82189.299 ops/s
[info] UTF8EncodingBench.encode_ok_lenient_utf8 thrpt 40 18988721.230 ± 113320.436 ops/s
```
This is quite promising, it looks like our new codec is closer to baseline best performance than `Scodec`'s, also it
appears that it beats baseline's ByteBuffer's long string encoding/decoding.
What follows is the benchmark on `Codec[RESP]` after swapping UTF8 codec:
```bash
sbt:laserdisc> cd protocol-benchmarks
sbt:protocol-bench> jmh:run -i 20 -wi 10 -f2 -t1 .*RESPBench.*
[info] Benchmark Mode Cnt Score Error Units
[info] RESPBench.baseline_utf8_decode thrpt 40 586764.974 ± 5038.563 ops/s
[info] RESPBench.baseline_utf8_decodeI thrpt 40 589099.238 ± 3715.503 ops/s
[info] RESPBench.baseline_utf8_encode thrpt 40 217015.042 ± 1349.796 ops/s
[info] RESPBench.baseline_utf8_encodeI thrpt 40 218071.397 ± 1094.907 ops/s
[info] RESPBench.bulkString_decode thrpt 40 432141.362 ± 3520.454 ops/s
[info] RESPBench.bulkString_encode thrpt 40 720834.209 ± 3055.016 ops/s
[info] RESPBench.error_decode thrpt 40 673011.802 ± 12557.678 ops/s
[info] RESPBench.error_encode thrpt 40 9749003.374 ± 282563.535 ops/s
[info] RESPBench.integer_decode thrpt 40 1415915.082 ± 24965.637 ops/s
[info] RESPBench.integer_encode thrpt 40 9622437.551 ± 231010.857 ops/s
[info] RESPBench.simpleString_decode thrpt 40 2005482.712 ± 67972.508 ops/s
[info] RESPBench.simpleString_encode thrpt 40 9792709.937 ± 83026.183 ops/s
```
Aside for the fact that we pretty much bumped upwards all figures, we managed to significantly increase `BulkString`
encoding raw performance to being _~13 times_ slower than `SimpleString` encoding, which is way more acceptable than
before (also remembering that `SimpleString` is encoding a string 1000 smaller in length).
**Huzzah!**
## On charsets... _(08 May 2018)_
Now that decoding is faster, let's try and figure out what is going on
with encoding. This seems to be our new bottleneck.
To measure its performance, we added baseline performance of the underlying codec
used: [scodec.codecs.utf8](https://github.com/scodec/scodec/blob/v1.10.3/shared/src/main/scala/scodec/codecs/package.scala#L532)
To further ensure we were not hampering our original scenario (and our baseline) by using `String`s that were constructed
upon class instantiation via new object creation we added string [interns](https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.5)
in the mix (the `encodeI` method)
Another run of jmh (_upgraded to latest 1.21_) revealed that
```bash
sbt:laserdisc> cd protocol-benchmarks
sbt:protocol-bench> jmh:run -i 20 -wi 10 -f2 -t1 .*RESPBench.b.*encode
[info] REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
[info] why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
[info] experiments, perform baseline and negative tests that provide experimental control, make sure
[info] the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
[info] Do not assume the numbers tell you what you want them to tell.
[info] Benchmark Mode Cnt Score Error Units
[info] RESPBench.baseline_utf8_encode thrpt 40 216589.943 ± 1207.118 ops/s
[info] RESPBench.baseline_utf8_encodeI thrpt 40 217304.295 ± 463.766 ops/s
[info] RESPBench.bulkString_encode thrpt 40 206098.693 ± 417.478 ops/s
```
This means that indeed `BulkString` encoding is slow, but not more than `~5%` slower than bare `scodec.codecs.utf8`'s
**for the same data**. Also, this reveals that indeed intern string encoding is slightly faster, as expected.
One now may ask: is SimpleString encoding doing something dramatically different that the rest given that it is
_~30 times_ faster?
Let's [flame-graph](https://github.com/brendangregg/FlameGraph) them all!
### BulkString
![BulkString encoding](flamegraphs/bulk-string-encode-flame-graph-cpu-20180508.svg)
### Baseline (scodec.codecs.utf8)
![utf8 encoding](flamegraphs/baseline-string-encode-flame-graph-cpu-20180508.svg)
### SimpleString
![SimpleString encoding](flamegraphs/simple-string-encode-flame-graph-cpu-20180508.svg)
Ok, so, what's there? All of these share pretty much the same code path, from going through `scodec.codecs.StringCodec.encode`
through `java.nio.charset.CharsetEncoder.encode` ultimately to `sun.nio.cs.UTF_8$Encoder.encodeLoop` and
`sun.nio.cs.UTF_8$Encoder.encodeBufferLoop`.
The difference is the **time spent** in those, which accounts for **>63% in `BulkString` and `Baseline` cases** and
**only 6% in `SimpleString`'s case**!!
In `SimpleString`'s case the string converted to `UTF-8` is `OK`, whilst in `BulkString` and `Baseline` its a string of
length `2000` repeating the character `a`.
It appears that a `1000` factor increase in string length produces approximately a `30` factor reduction in throughput...
bummer!
Next: see what can be done to optimise UTF8 encoding...
## Results after first optimisation round _(05 May 2018)_
```bash
sbt:laserdisc> cd protocol-benchmarks
sbt:protocol-bench> jmh:run -i 20 -wi 10 -f2 -t1 .*
[info] Benchmark Mode Cnt Score Error Units
[info] ProtocolBench.decode thrpt 40 4657288.663 ± 81320.290 ops/s
[info] ProtocolBench.encode thrpt 40 4380152.108 ± 49000.048 ops/s
[info] RESPBench.bulkString_decode thrpt 40 338781.141 ± 5180.365 ops/s
[info] RESPBench.bulkString_encode thrpt 40 201540.665 ± 1443.579 ops/s
[info] RESPBench.error_decode thrpt 40 635168.747 ± 8633.595 ops/s
[info] RESPBench.error_encode thrpt 40 5023689.984 ± 86893.815 ops/s
[info] RESPBench.integer_decode thrpt 40 1380625.705 ± 13065.164 ops/s
[info] RESPBench.integer_encode thrpt 40 7251347.150 ± 183302.953 ops/s
[info] RESPBench.simpleString_decode thrpt 40 1849976.081 ± 44952.587 ops/s
[info] RESPBench.simpleString_encode thrpt 40 6375721.979 ± 46999.275 ops/s
[info] RESPParamWriteBench.write thrpt 40 9591938.499 ± 314472.561 ops/s
```
## Initial results _(04 May 2018)_
```bash
sbt:laserdisc> cd protocol-benchmarks
sbt:protocol-bench> jmh:run -i 20 -wi 10 -f2 -t1 .*
[info] Benchmark Mode Cnt Score Error Units
[info] ProtocolBench.decode thrpt 40 4415274.330 ± 62501.103 ops/s
[info] ProtocolBench.encode thrpt 40 4093451.944 ± 68472.615 ops/s
[info] RESPBench.bulkString_decode thrpt 40 316.769 ± 9.826 ops/s
[info] RESPBench.bulkString_encode thrpt 40 161260.084 ± 4011.619 ops/s
[info] RESPBench.error_decode thrpt 40 33473.536 ± 677.165 ops/s
[info] RESPBench.error_encode thrpt 40 5390220.406 ± 493286.825 ops/s
[info] RESPBench.integer_decode thrpt 40 206950.255 ± 5283.544 ops/s
[info] RESPBench.integer_encode thrpt 40 6658282.051 ± 98604.929 ops/s
[info] RESPBench.simpleString_decode thrpt 40 220737.683 ± 4632.795 ops/s
[info] RESPBench.simpleString_encode thrpt 40 5707122.965 ± 276588.267 ops/s
[info] RESPParamWriteBench.write thrpt 40 6886022.467 ± 243744.210 ops/s
```
Results "seem" to indicate that decoding is _sloooooow_ :)
In particular, BulkString decoding is extremely slow.
Considering that most of Redis' responses are Arrays of BulkStrings... and we're not yet even microbenchmarking Arrays yet...
Oops, something went wrong.

0 comments on commit 2aac64d

Please sign in to comment.