mdedetrich / scalajson

ScalaJSON - JSON for Scala, currently contains minimal AST
BSD 3-Clause "New" or "Revised" License
55 stars 10 forks source link

Speed up JNumber Array[Char] => String creation by ~3x. #26

Closed htmldoug closed 7 years ago

htmldoug commented 7 years ago

This is a more aggressive version of https://github.com/mdedetrich/scalajson/pull/25.

Summary

I was curious about the cost of Array[Char].mkString vs new String(Array[Char]), so I benchmarked them.

Using the String constructor directly is roughly 3x faster.

Results

Greatest benefit was seen for small strings, which I'd expect to be the common case for json processing.

[info] Benchmark                                  (len)   (method)   Mode  Cnt          Score          Error  Units
[info] StringBuildingBenchmark.arrayCharToString      1   mkstring  thrpt   30   22002307.228 ±   345191.252  ops/s
[info] StringBuildingBenchmark.arrayCharToString      1  newString  thrpt   30  109893200.111 ±  2081134.721  ops/s
[info] StringBuildingBenchmark.arrayCharToString      4   mkstring  thrpt   30   41137855.368 ±   855379.695  ops/s
[info] StringBuildingBenchmark.arrayCharToString      4  newString  thrpt   30  111457754.148 ±  1161271.794  ops/s
[info] StringBuildingBenchmark.arrayCharToString     16   mkstring  thrpt   30   31449261.979 ±  8122970.995  ops/s
[info] StringBuildingBenchmark.arrayCharToString     16  newString  thrpt   30   95994095.806 ± 12252876.044  ops/s
[info] StringBuildingBenchmark.arrayCharToString   4096   mkstring  thrpt   30   35797634.580 ±  4636664.117  ops/s
[info] StringBuildingBenchmark.arrayCharToString   4096  newString  thrpt   30   90919112.072 ± 15096548.293  ops/s

Benchmark

This project uses scalameter, but jmh is what I know. Here's the benchmark.

class StringBuildingBenchmark {

  @Benchmark
  @BenchmarkMode(Array(Mode.Throughput))
  def arrayCharToString(state: StringBuildingBenchmarkState): String = {
    state.constructString(state.charArray)
  }
}

@State(Scope.Benchmark)
class StringBuildingBenchmarkState {

  @Param(Array("1", "4", "16", "4096"))
  var len: Int = _

  @Param(Array("mkstring", "newString"))
  var method: String = _

  var constructString: Array[Char] => String = _

  val charArray: Array[Char] = Iterator.continually('9').take(len).toArray

  @Setup
  def setup(): Unit = {
    method match {
      case "mkstring" => constructString = _.mkString
      case "newString" => constructString = new String(_)
    }
  }
}
codecov-io commented 7 years ago

Codecov Report

Merging #26 into master will increase coverage by 0.15%. The diff coverage is 66.66%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #26      +/-   ##
==========================================
+ Coverage   44.64%   44.79%   +0.15%     
==========================================
  Files           5        5              
  Lines         663      663              
  Branches      140      140              
==========================================
+ Hits          296      297       +1     
+ Misses        367      366       -1
Impacted Files Coverage Δ
...s/src/main/scala/scalajson/ast/unsafe/JValue.scala 0% <0%> (ø) :arrow_up:
...m/src/main/scala/scalajson/ast/unsafe/JValue.scala 60.64% <100%> (ø) :arrow_up:
jvm/src/main/scala/scalajson/ast/JValue.scala 73% <100%> (+1%) :arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update d863974...3a323b3. Read the comment docs.