├── .gitignore ├── LICENSE ├── README.org ├── build.sbt ├── project ├── build.properties └── plugins.sbt └── src └── main └── scala └── benchmarks ├── AccumClassBench.scala ├── ConcatBench.scala ├── FlattenBench.scala ├── FoldBench.scala ├── FoldClassBench.scala ├── MapBench.scala ├── MatchBench.scala ├── MatchContainersBench.scala ├── SetBench.scala ├── SortBench.scala ├── StreamBench.scala ├── UniquesBench.scala └── VectorBench.scala /.gitignore: -------------------------------------------------------------------------------- 1 | /RUNNING_PID 2 | /logs/ 3 | /project/*-shim.sbt 4 | /project/project/ 5 | /project/target/ 6 | /target/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Typesafe, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Scala Benchmarks 2 | #+AUTHOR: Colin 3 | 4 | An independent set of benchmarks for testing common Scala idioms. 5 | 6 | * Table of Contents :TOC_4_gh:noexport: 7 | - [[#functional-programming][Functional Programming]] 8 | - [[#folding][Folding]] 9 | - [[#chained-higher-order-functions][Chained Higher-Order Functions]] 10 | - [[#concatenation][Concatenation]] 11 | - [[#mutable-data][Mutable Data]] 12 | - [[#list-ilist-and-array][List, IList and Array]] 13 | - [[#builder-classes][Builder Classes]] 14 | - [[#mutable-set-and-javas-concurrenthashmap][Mutable Set and Java's ConcurrentHashMap]] 15 | - [[#pattern-matching][Pattern Matching]] 16 | - [[#deconstructing-containers][Deconstructing Containers]] 17 | - [[#guard-patterns][Guard Patterns]] 18 | 19 | * Functional Programming 20 | 21 | ** Folding 22 | 23 | We often want to collapse a collection into some summary of its elements. 24 | This is known as a /fold/, a /reduce/, or a /catamorphism/: 25 | 26 | #+BEGIN_SRC scala 27 | List(1,2,3).foldLeft(0)(_ + _) // 6 28 | #+END_SRC 29 | 30 | How fast is this operation in the face of the JVM's ~while~ and mutable 31 | variables? For instance the familiar, manual, and error-prone: 32 | 33 | #+BEGIN_SRC scala 34 | var n: Int = 0 35 | var i: Int = coll.length - 1 36 | 37 | while (i >= 0) { 38 | n += coll(i) 39 | i -= 1 40 | } 41 | #+END_SRC 42 | 43 | ~FoldBench~ compares ~List~, ~scalaz.IList~, ~Vector~, ~Array~, ~Stream~, and 44 | ~Iterator~ for their speeds in various fold operations over Ints. 45 | ~FoldClassBench~ tries these same operations over a simple wrapping class to see 46 | how boxing/references affect things. 47 | 48 | ~Int~ Results: 49 | 50 | /All times are in microseconds. Entries marked with an asterisk are sped up by 51 | optimization flags. Entries marked with two are slowed down by them./ 52 | 53 | | Benchmark | List | IList | Vector | Array | Stream | EStream | Iterator | 54 | |----------------+--------+-------+--------+-------+----------------+----------------+----------| 55 | | ~foldLeft~ | 44.1** | 31.3 | 63.5 | 34.0* | 56.9 | 180.3** | 55.4 | 56 | | ~foldRight~ | 69.2 | 81.9 | 137.9* | 36.3* | Stack Overflow | Stack Overflow | 147.6 | 57 | | Tail Recursion | 45.9 | 24.1 | | | 69.8 | | | 58 | | ~sum~ | 76.9 | | 71.0 | 79.0 | 74.7 | | | 59 | | ~while~ | 44.0 | | 38.4 | 3.0 | 52.9 | | 45.4 | 60 | 61 | ~Pair~ Class Results: 62 | 63 | /All times are in microseconds./ 64 | 65 | | Benchmark | List | IList | Vector | Array | Stream | Iterator | 66 | |----------------+------+-------+--------+-------+----------------+----------| 67 | | ~foldLeft~ | 39.5 | 37.5 | 70.2 | 39.9 | 68.2 | 65.8 | 68 | | ~foldRight~ | 83.6 | 98.1 | 242.1 | 38.8 | Stack Overflow | 157.3 | 69 | | Tail Recursion | 39.2 | 37.9 | | | 118.6** | | 70 | | ~while~ | 39.3 | | 57.8 | 36.2 | 70.1 | 39.2 | 71 | 72 | Observations: 73 | 74 | - ~foldLeft~ is always better than both ~foldRight~ and manual tail recursion for 75 | catamorphisms (reduction to a single value). 76 | - ~sum~ should be avoided. 77 | - ~Iterator~ benefits from ~while~, but not enough to beat ~List~. 78 | - Collections with random access (especially ~Array~) benefit from ~while~ 79 | loops. 80 | - *Array has no advantage over List when holding non-primitive types!* 81 | 82 | Recommendation: 83 | 84 | #+BEGIN_QUOTE 85 | ~List.foldLeft~ is concise and performant for both primitive and boxed types. 86 | If you were already dealing with an ~Array[Int]~ or likewise, then a ~while~ 87 | loop will be faster. 88 | #+END_QUOTE 89 | 90 | ** Chained Higher-Order Functions 91 | 92 | It's common to string together multiple operations over a collection, say: 93 | 94 | #+BEGIN_SRC scala 95 | List(1,2,3,4).map(foo).filter(pred).map(bar) 96 | #+END_SRC 97 | 98 | which is certainly shorter and cleaner in its intent than manually manipulating 99 | a mutable collection in a ~while~ loop. Are higher-order operations like these 100 | still fast? People used to Haskell's list fusion might point out that these 101 | operations typically don't fuse in Scala, meaning that each chained operation 102 | fully iterates over the entire collection and allocates a new copy. ~Stream~ and 103 | ~Iterator~ are supposed to be the exceptions, however. 104 | 105 | ~Stream~ in particular is what people wanting Haskell's lazy lists may reach for 106 | first, on the claim that the elements memoize, chained operations fuse, and they 107 | support infinite streams of values. Let's see how everything performs. 108 | 109 | ~StreamBench~ performs the following operations on ~List~, ~scalaz.IList~, 110 | ~Vector~, ~Array~, ~Stream~, ~scalaz.EphemeralStream~ and ~Iterator~. We test: 111 | 112 | - /Head/: map-filter-map-head. Which collections "short-circuit", only 113 | fully processing the head and nothing else? 114 | - /Max/: map-filter-map-max. How quickly can each collection fully process itself? 115 | Does fusion occur (esp. with ~Stream~)? 116 | - /Reverse/: reverse-head. Can any of the collections "cheat" and grab the last 117 | element quickly? 118 | - /Sort/: map-filter-map-sorted-head. Does ~Stream~ still leverage laziness with 119 | a "mangling" operation like sort? 120 | 121 | Results: 122 | 123 | /All times are in microseconds./ 124 | 125 | | Benchmark | List | IList | Vector | Array | Stream | EStream | Iterator | 126 | |-----------+-------+-------+--------+-------+--------+---------+----------| 127 | | Head | 182.3 | 273.2 | 133.2 | 206.3 | 0.065 | 0.17 | 0.023 | 128 | | Max | 198.9 | 401.7 | 263.5 | 192.7 | 863.7 | 1714.4 | 139.7 | 129 | | Reverse | 37.8 | 49.2 | 146.7 | 45.6 | 371.6 | 448.5 | | 130 | | Sort | 327.5 | 607.6 | 277.8 | 289.4 | 1482.8 | | | 131 | 132 | Observations: 133 | 134 | - ~Stream~ won't do work it doesn't have to, as advertised (re: /Head/). 135 | - ~Stream~ is very slow to fully evaluate, implying no operation fusion. 136 | Nothing clever happens with sorting. 137 | - ~Iterator~ overall is the fastest collection to chain higher-order 138 | functions. 139 | - ~List~ has the fastest ~reverse~. 140 | 141 | Recommendation: 142 | 143 | #+BEGIN_QUOTE 144 | If you want to chain higher-order operations in Scala, use an ~Iterator~. 145 | If you have something like a ~List~ instead, create an ~Iterator~ first 146 | with ~.iterator~ before you chain. 147 | #+END_QUOTE 148 | 149 | ** Concatenation 150 | 151 | Sometimes we need to merge two instances of a container together, end-to-end. 152 | This is embodied by the classic operator ~++~, available for all the major 153 | collection types. 154 | 155 | We know that the collection types are implemented differently. Are some better 156 | than others when it comes to ~++~? For instance, we might imagine that the 157 | singly-linked ~List~ type would be quite bad at this. The lazy ~Stream~ types 158 | should be instantaneous. 159 | 160 | ~ConcatBench~ tests ~List~, ~scalaz.IList~, ~Array~, ~Vector~, ~Stream~, and 161 | ~scalaz.EphemeralStream~ for their performance with the ~++~ operator. Two 162 | results are offered for ~Array~: one with ~Int~ and one for a simple ~Pair~ 163 | class, to see if primitive Arrays can somehow be optimized here by the JVM, as 164 | they usually are. Otherwise, the results are all for collections of ~Int~. 165 | 166 | /All times are in microseconds./ 167 | 168 | | Item Count | ~List~ | ~IList~ | ~Vector~ | ~Array[Int]~ | ~Array[Pair]~ | ~Stream~ | ~EStream~ | 169 | |------------+--------+---------+----------+--------------+---------------+----------+-----------| 170 | | 1,000 | 14 | 10 | 17 | 0.6 | 0.7 | 0.02 | 0.02 | 171 | | 10,000 | 117 | 78 | 147 | 7 | 7 | 0.02 | 0.02 | 172 | | 100,000 | 931 | 993 | 1209 | 75 | 77 | 0.02 | 0.02 | 173 | | 1,000,000 | 8506 | 10101 | 10958 | 1777 | 1314 | 0.02 | 0.02 | 174 | 175 | Observations: 176 | 177 | - The ~Stream~ types were instantaneous, as expected. 178 | - ~Array~ is quick! Somehow quicker for classes, though. 179 | - The drastic slowdown for ~Array~ at the millions-of-elements scale is strange. 180 | - ~IList~ beats ~List~ until millions-of-elements scale. 181 | - ~Vector~ has no advantage here, despite rumours to the contrary. 182 | 183 | Recommendation: 184 | 185 | #+BEGIN_QUOTE 186 | If your algorithm requires concatenation of large collections, use ~Array~. 187 | If you're worried about passing a mutable collection around your API, consider 188 | ~scalaz.ImmutableArray~, a simple wrapper that prevents careless misuse. 189 | #+END_QUOTE 190 | 191 | * Mutable Data 192 | 193 | ** List, IList and Array 194 | 195 | Above we saw that ~List~ performs strongly against ~Array~ when it comes to 196 | chaining multiple higher-order functions together. What happens when we just 197 | need to make a single transformation pass over our collection - in other words, 198 | a ~.map~? ~Array~ with a ~while~ loop is supposed to be the fastest iterating 199 | operation on the JVM. Can ~List~ and ~IList~ still stand up to it? 200 | 201 | ~MapBench~ compares these operations over increasing larger collection sizes of 202 | both ~Int~ and a simple wrapper class. 203 | 204 | Results: 205 | 206 | /All times are in microseconds./ 207 | 208 | | Benchmark | ~List.map~ | ~IList.map~ | ~Array~ + ~while~ | 209 | |---------------+------------+-------------+-------------------| 210 | | 100 Ints | 0.77 | 1.1 | 0.05 | 211 | | 1000 Ints | 7.8 | 10.9 | 0.45 | 212 | | 10000 Ints | 71.6 | 99.9 | 3.7 | 213 | |---------------+------------+-------------+-------------------| 214 | | 100 Classes | 0.83 | 1.3 | 0.4 | 215 | | 1000 Classes | 8.6 | 12.9 | 4.3 | 216 | | 10000 Classes | 81.3 | 111.2 | 43.1 | 217 | 218 | Observations: 219 | 220 | - For ~List~, there isn't too much difference between Ints and classes. 221 | - ~Array~ is fast to do a single-pass iteration. 222 | 223 | Recommendation: 224 | 225 | #+BEGIN_QUOTE 226 | If your code involves ~Array~, primitives, and simple single-pass 227 | transformations, then ~while~ loops will be fast for you. Otherwise, your code 228 | will be cleaner and comparitively performant if you stick to immutable 229 | collections and chained higher-order functions. 230 | #+END_QUOTE 231 | 232 | ** Builder Classes 233 | 234 | You want to build up a new collection, perhaps iterating over an existing one, 235 | perhaps from some live, dynamic process. For whatever reason ~.map~ and 236 | ~.foldLeft~ are not an option. Which collection is best for this? ~VectorBench~ 237 | tests how fast each of ~List~, ~scalaz.IList~, ~ListBuffer~, ~Vector~, 238 | ~VectorBuilder~, ~Array~, ~ArrayBuilder~, and ~IndexedSeq~ can create themselves 239 | and accumulate values. For ~List~, this is done with tail recursion. For 240 | ~IndexedSeq~, this is done via a naive for-comprehension. For all others, this 241 | is done with ~while~ loops. The ~Buffer~ and ~Builder~ classes perform a 242 | ~.result~ call at the end of iterating to take their non-builder forms (i.e. 243 | ~VectorBuilder => Vector~). ~ArrayBuilder~ is given an overshot size hint (with 244 | ~.sizeHint~) in order to realistically minimize inner ~Array~ copying. 245 | 246 | Results: 247 | 248 | /All times are in microseconds./ 249 | 250 | | Benchmark | ~List~ | ~IList~ | ~ListBuffer~ | ~Vector~ | ~VectorBuilder~ | ~Array~ | ~ArrayBuilder~ | ~IndexedSeq~ | 251 | |----------------+--------+---------+--------------+----------+-----------------+---------+----------------+--------------| 252 | | 1000 Ints | 5.7 | 5.5 | 5.5 | 20.8 | 6.6 | 0.6 | 1.1 | 5.9 | 253 | | 10000 Ints | 60.2 | 57.1 | 57.9 | 206.1 | 39.0 | 5.3 | 11.4 | 61.4 | 254 | | 100000 Ints | 545.1 | 529.1 | 551.6 | 2091.2 | 384.3 | 53.3 | 121.3 | 615.3 | 255 | | 1000 Classes | 6.2 | 6.2 | 7.2 | 21.5 | 6.3 | 3.8 | 4.9 | 6.4 | 256 | | 10000 Classes | 64.4 | 62.4 | 68.5 | 214.3 | 44.7 | 41.4 | 53.1 | 65.4 | 257 | | 100000 Classes | 592.0 | 600.3 | 611.6 | 2164.7 | 429.4 | 357.0 | 523.5 | 653.3 | 258 | 259 | Observations: 260 | 261 | - For primitives, ~Array~ is king. 262 | - *Avoid appending to immutable Vectors.* 263 | - *Avoid repeated use of ListBuffer.prepend!* Your runtime will slow by an order of magnitude vs ~+=:~. 264 | - For classes, at small scales (~1000 elements) there is mostly no difference between 265 | the various approaches. 266 | - ~ArrayBuilder~ can be useful if you're able to ballpark what the final result size will be. 267 | - ~VectorBuilder~ fulfills the promise of Builders, but can only append to the right. 268 | You'd have to deal with the fact that your elements are reversed. 269 | 270 | Recommendation: 271 | 272 | #+BEGIN_QUOTE 273 | The best choice here depends on what your next step is. 274 | 275 | If you plan to perform ~while~ -based numeric calculations over primitives only, 276 | stick to ~Array~. If using ~ArrayBuilder~ with primitives, avoid the ~.make~ 277 | method. Use something like ~.ofInt~ instead. Also make sure that you use 278 | ~.sizeHint~ to avoid redundant inner ~Array~ copying as your collection grows. 279 | Failing to do so can introduce a 5x slowdown. 280 | 281 | Otherwise, consider whether your algorithm can't be reexpressed entirely in 282 | terms of ~Iterator~. This will always give the best performance for subsequent 283 | chained, higher-order functions. 284 | 285 | If the algorithm can't be expressed in terms of ~Iterator~ from the get-go, try 286 | building your collection with ~VectorBuilder~, call ~.iterator~ once filled, 287 | then continue. 288 | #+END_QUOTE 289 | 290 | ** Mutable Set and Java's ConcurrentHashMap 291 | 292 | You'd like to build up a unique set of values and for some reason calling 293 | ~.toSet~ on your original collection isn't enough. Perhaps you don't have an 294 | original collection. Scala's collections have been criticized for their 295 | performance, with one famous complaint saying how their team had to fallback to 296 | using Java collection types entirely because the Scala ones couldn't compare 297 | (that was for Scala 2.8, mind you). 298 | 299 | Is this true? ~UniquesBench~ compares both of Scala's mutable and immutable 300 | ~Set~ types with Java's ~ConcurrentHashMap~ to see which can accumulate unique 301 | values faster. 302 | 303 | Results: 304 | 305 | /All values are in microseconds./ 306 | 307 | | Benchmark | ~mutable.Set~ | ~immutable.Set~ | Java ~ConcurrentHashMap~ | 308 | |--------------+---------------+-----------------+--------------------------| 309 | | 100 values | 4.6 | 7.7 | 6.1 | 310 | | 1000 values | 62.2 | 107.4 | 71.3 | 311 | | 10000 values | 811.1* | 1290.4 | 777.1 | 312 | 313 | *Note*: About half the time the 10000-value benchmark for ~mutable.Set~ 314 | optimizes down to ~600us instead of the ~800us shown in the chart. 315 | 316 | Observations: 317 | 318 | - ~mutable.Set~ is fastest at least for small amounts of data, and /might/ be 319 | fastest at scale. 320 | - ~immutable.Set~ is slower and has worse growth, as expected. 321 | 322 | Recommendation: 323 | 324 | #+BEGIN_QUOTE 325 | First consider whether your algorithm can't be rewritten in terms of the usual 326 | FP idioms, followed by a ~.toSet~ call to make the collection unique. 327 | 328 | If that isn't possible, then trust in the performance of native Scala 329 | collections and use ~mutable.Set~. 330 | #+END_QUOTE 331 | 332 | * Pattern Matching 333 | 334 | ** Deconstructing Containers 335 | 336 | It's common to decontruct containers like this in recursive algorithms: 337 | 338 | #+BEGIN_SRC scala 339 | def safeHead[A](s: Seq[A]): Option[A] = s match { 340 | case Seq() => None 341 | case h +: _ => Some(h) 342 | } 343 | #+END_SRC 344 | 345 | But ~List~ and ~Stream~ have special "cons" operators, namely ~::~ and ~#::~ 346 | respectively. The ~List~ version of the above looks like: 347 | 348 | #+BEGIN_SRC scala 349 | def safeHead[A](l: List[A]): Option[A] = l match { 350 | case Nil => None 351 | case h :: _ => Some(h) 352 | } 353 | #+END_SRC 354 | 355 | How do these operators compare? Also, is it any slower to do it this way than a 356 | more Java-like: 357 | 358 | #+BEGIN_SRC scala 359 | def safeHead[A](l: List[A]): Option[A] = 360 | if (l.isEmpty) None else l.head 361 | #+END_SRC 362 | 363 | The ~MatchContainersBench~ benchmarks use a tail-recursive algorithm to find the 364 | last element of each of ~List~, ~scalaz.IList~, ~Vector~, ~Array~, ~Seq~, and 365 | ~Stream~. 366 | 367 | Results: 368 | 369 | /All times are in microseconds./ 370 | 371 | | Benchmark | List | IList | Vector | Seq | Array | Stream | 372 | |-----------------+------+-------+--------+-------+---------+--------| 373 | | ~::~ Matching | 42.8 | 23.6 | | | | 168.4 | 374 | | ~+:~ Matching | 79.0 | | 1647.5 | 707.4 | | 170.2 | 375 | | ~if~ Statements | 39.9 | | 816.9 | 39.4 | 16020.6 | 55.8 | 376 | 377 | Observations: 378 | 379 | - Canonical ~List~ and ~IList~ matching is /fast/. 380 | - ~Seq~ matching with ~+:~, its canonical operator, is ironically slow. 381 | - Pattern matching with ~+:~ should be avoided in general. 382 | - ~if~ is generally faster than pattern matching, but the code isn't as nice. 383 | - Avoid recursion with ~Vector~ and ~Array~! 384 | - ~Array.tail~ is pure evil. Each call incurs ~ArrayOps~ wrapping and 385 | seems to reallocate the entire ~Array~. ~Vector.tail~ incurs a similar 386 | slowdown, but not as drasticly. 387 | 388 | Recommendation: 389 | 390 | #+BEGIN_QUOTE 391 | Recursion involving containers should be done with ~List~ and pattern matching 392 | for the best balance of speed and simplicity. If you can take ~scalaz~ as a 393 | dependency, its ~IList~ will be even faster. 394 | #+END_QUOTE 395 | ** Guard Patterns 396 | 397 | It can sometimes be cleaner to check multiple ~Boolean~ conditions using a ~match~: 398 | 399 | #+BEGIN_SRC scala 400 | def foo(i: Int): Whatever = i match { 401 | case _ if bar(i) => ??? 402 | case _ if baz(i) => ??? 403 | case _ if zoo(i) => ??? 404 | case _ => someDefault 405 | } 406 | #+END_SRC 407 | 408 | where we don't really care about the pattern match, just the guard. This is in 409 | constrast to ~if~ branches: 410 | 411 | #+BEGIN_SRC scala 412 | def foo(i: Int): Whatever = { 413 | if (bar(i)) ??? 414 | else if (baz(i)) ??? 415 | else if (zoo(i)) ??? 416 | else someDefault 417 | } 418 | #+END_SRC 419 | 420 | which of course would often be made more verbose by many ~{}~ pairs. Are we 421 | punished for the empty pattern matches? ~MatchBench~ tests this, with various 422 | numbers of branches. 423 | 424 | Results: 425 | 426 | /All times are in nanoseconds./ 427 | 428 | | Benchmark | Guards | Ifs | 429 | |--------------+--------+-----| 430 | | 1 Condition | 3.3 | 3.3 | 431 | | 2 Conditions | 3.6 | 3.6 | 432 | | 3 Conditions | 3.9 | 3.9 | 433 | 434 | Identical! Feel free to use whichever you think is cleaner. 435 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := """scala-benchmarks""" 2 | 3 | version := "1.0.0" 4 | 5 | scalaVersion := "2.12.8" 6 | 7 | scalacOptions := Seq( 8 | "-opt:l:inline", 9 | "-opt-inline-from:**", 10 | "-deprecation", 11 | "-Ypartial-unification", 12 | "-Ywarn-value-discard", 13 | "-Ywarn-unused-import", 14 | "-Ywarn-dead-code", 15 | "-Ywarn-numeric-widen" 16 | ) 17 | 18 | libraryDependencies ++= Seq( 19 | "org.scalaz" %% "scalaz-core" % "7.2.27" 20 | ) 21 | 22 | /* To run benchmarks: 23 | jmh:run -t 1 -f 1 -wi 5 -i 5 .*Bench.* 24 | */ 25 | enablePlugins(JmhPlugin) 26 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.6") 2 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/AccumClassBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import scala.annotation.tailrec 6 | import scala.collection.immutable.VectorBuilder 7 | import scala.collection.mutable.{ArrayBuilder, ListBuffer} 8 | 9 | import org.openjdk.jmh.annotations._ 10 | import scalaz.{IList, ICons, INil} 11 | 12 | // --- // 13 | 14 | @BenchmarkMode(Array(Mode.AverageTime)) 15 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 16 | @State(Scope.Thread) 17 | class AccumClassBench { 18 | 19 | def array(n: Int): Array[Pair] = { 20 | val a: Array[Pair] = new Array(n) 21 | var i: Int = 0 22 | 23 | while (i < n) { 24 | a(i) = Pair(i, i) 25 | i += 1 26 | } 27 | 28 | a 29 | } 30 | 31 | def abuilder(n: Int): Array[Pair] = { 32 | val b = ArrayBuilder.make[Pair] 33 | var i: Int = 0 34 | 35 | b.sizeHint(n * 2) 36 | 37 | while (i < n) { 38 | b += Pair(i, i) 39 | i += 1 40 | } 41 | 42 | b.result 43 | } 44 | 45 | def vector(n: Int): Vector[Pair] = { 46 | val v = Vector.empty[Pair] 47 | var i: Int = 0 48 | 49 | while (i < n) { 50 | Pair(i, i) +: v 51 | i += 1 52 | } 53 | 54 | v 55 | } 56 | 57 | def vbuilder(n: Int): Vector[Pair] = { 58 | val b = new VectorBuilder[Pair] 59 | var i: Int = 0 60 | 61 | while (i < n) { 62 | b += Pair(i, i) 63 | i += 1 64 | } 65 | 66 | b.result 67 | } 68 | 69 | def list(n: Int): List[Pair] = { 70 | 71 | @tailrec def work(acc: List[Pair], m: Int): List[Pair] = m match { 72 | case _ if m == n => acc 73 | case _ => work(Pair(m, m) :: acc, m + 1) 74 | } 75 | 76 | work(Nil, 0) 77 | } 78 | 79 | def ilist(n: Int): IList[Pair] = { 80 | 81 | @tailrec def work(acc: IList[Pair], m: Int): IList[Pair] = m match { 82 | case _ if m == n => acc 83 | case _ => work(ICons(Pair(m, m), acc), m + 1) 84 | } 85 | 86 | work(INil(), 0) 87 | } 88 | 89 | def buffer(n: Int): List[Pair] = { 90 | val b = new ListBuffer[Pair] 91 | var i: Int = 0 92 | 93 | while (i < n) { 94 | Pair(i, i) +=: b 95 | i += 1 96 | } 97 | 98 | b.result 99 | } 100 | 101 | def foryield(n: Int): IndexedSeq[Pair] = for (i <- 0 to n) yield Pair(i, i) 102 | 103 | @Benchmark 104 | def array1000: Array[Pair] = array(1000) 105 | @Benchmark 106 | def array10000: Array[Pair] = array(10000) 107 | @Benchmark 108 | def array100000: Array[Pair] = array(100000) 109 | 110 | @Benchmark 111 | def abuilder1000: Array[Pair] = abuilder(1000) 112 | @Benchmark 113 | def abuilder10000: Array[Pair] = abuilder(10000) 114 | @Benchmark 115 | def abuilder100000: Array[Pair] = abuilder(100000) 116 | 117 | @Benchmark 118 | def vector1000: Vector[Pair] = vector(1000) 119 | @Benchmark 120 | def vector10000: Vector[Pair] = vector(10000) 121 | @Benchmark 122 | def vector100000: Vector[Pair] = vector(100000) 123 | 124 | @Benchmark 125 | def vbuilder1000: Vector[Pair] = vbuilder(1000) 126 | @Benchmark 127 | def vbuilder10000: Vector[Pair] = vbuilder(10000) 128 | @Benchmark 129 | def vbuilder100000: Vector[Pair] = vbuilder(100000) 130 | 131 | @Benchmark 132 | def list1000: List[Pair] = list(1000) 133 | @Benchmark 134 | def list10000: List[Pair] = list(10000) 135 | @Benchmark 136 | def list100000: List[Pair] = list(100000) 137 | 138 | @Benchmark 139 | def ilist1000: IList[Pair] = ilist(1000) 140 | @Benchmark 141 | def ilist10000: IList[Pair] = ilist(10000) 142 | @Benchmark 143 | def ilist100000: IList[Pair] = ilist(100000) 144 | 145 | @Benchmark 146 | def buffer1000: List[Pair] = buffer(1000) 147 | @Benchmark 148 | def buffer10000: List[Pair] = buffer(10000) 149 | @Benchmark 150 | def buffer100000: List[Pair] = buffer(100000) 151 | 152 | @Benchmark 153 | def foryield1000: IndexedSeq[Pair] = foryield(1000) 154 | @Benchmark 155 | def foryield10000: IndexedSeq[Pair] = foryield(10000) 156 | @Benchmark 157 | def foryield100000: IndexedSeq[Pair] = foryield(100000) 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/ConcatBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations._ 6 | import scalaz.{IList, EphemeralStream => EStream} 7 | 8 | // --- // 9 | 10 | @BenchmarkMode(Array(Mode.AverageTime)) 11 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 12 | @State(Scope.Thread) 13 | class ConcatBench { 14 | 15 | var list0: List[Int] = _ 16 | var list1: List[Int] = _ 17 | var list2: List[Int] = _ 18 | var list3: List[Int] = _ 19 | var list0b: List[Int] = _ 20 | var list1b: List[Int] = _ 21 | var list2b: List[Int] = _ 22 | var list3b: List[Int] = _ 23 | 24 | var ilist0: IList[Int] = _ 25 | var ilist1: IList[Int] = _ 26 | var ilist2: IList[Int] = _ 27 | var ilist3: IList[Int] = _ 28 | var ilist0b: IList[Int] = _ 29 | var ilist1b: IList[Int] = _ 30 | var ilist2b: IList[Int] = _ 31 | var ilist3b: IList[Int] = _ 32 | 33 | var vector0: Vector[Int] = _ 34 | var vector1: Vector[Int] = _ 35 | var vector2: Vector[Int] = _ 36 | var vector3: Vector[Int] = _ 37 | var vector0b: Vector[Int] = _ 38 | var vector1b: Vector[Int] = _ 39 | var vector2b: Vector[Int] = _ 40 | var vector3b: Vector[Int] = _ 41 | 42 | var array0: Array[Int] = _ 43 | var array1: Array[Int] = _ 44 | var array2: Array[Int] = _ 45 | var array3: Array[Int] = _ 46 | var array0b: Array[Int] = _ 47 | var array1b: Array[Int] = _ 48 | var array2b: Array[Int] = _ 49 | var array3b: Array[Int] = _ 50 | 51 | var arrayC0: Array[Pair] = _ 52 | var arrayC1: Array[Pair] = _ 53 | var arrayC2: Array[Pair] = _ 54 | var arrayC3: Array[Pair] = _ 55 | var arrayC0b: Array[Pair] = _ 56 | var arrayC1b: Array[Pair] = _ 57 | var arrayC2b: Array[Pair] = _ 58 | var arrayC3b: Array[Pair] = _ 59 | 60 | var stream0: Stream[Int] = _ 61 | var stream1: Stream[Int] = _ 62 | var stream2: Stream[Int] = _ 63 | var stream3: Stream[Int] = _ 64 | var stream0b: Stream[Int] = _ 65 | var stream1b: Stream[Int] = _ 66 | var stream2b: Stream[Int] = _ 67 | var stream3b: Stream[Int] = _ 68 | 69 | var estream0: EStream[Int] = _ 70 | var estream1: EStream[Int] = _ 71 | var estream2: EStream[Int] = _ 72 | var estream3: EStream[Int] = _ 73 | var estream0b: EStream[Int] = _ 74 | var estream1b: EStream[Int] = _ 75 | var estream2b: EStream[Int] = _ 76 | var estream3b: EStream[Int] = _ 77 | 78 | @Setup 79 | def setup: Unit = { 80 | list0 = List.range(1, 1000) 81 | list1 = List.range(1, 10000) 82 | list2 = List.range(1, 100000) 83 | list3 = List.range(1, 1000000) 84 | list0b = List.range(1, 1000) 85 | list1b = List.range(1, 10000) 86 | list2b = List.range(1, 100000) 87 | list3b = List.range(1, 1000000) 88 | 89 | ilist0 = IList.fromList(list0) 90 | ilist1 = IList.fromList(list1) 91 | ilist2 = IList.fromList(list2) 92 | ilist3 = IList.fromList(list3) 93 | ilist0b = IList.fromList(list0) 94 | ilist1b = IList.fromList(list1) 95 | ilist2b = IList.fromList(list2) 96 | ilist3b = IList.fromList(list3) 97 | 98 | vector0 = Vector.range(1, 1000) 99 | vector1 = Vector.range(1, 10000) 100 | vector2 = Vector.range(1, 100000) 101 | vector3 = Vector.range(1, 1000000) 102 | vector0b = Vector.range(1, 1000) 103 | vector1b = Vector.range(1, 10000) 104 | vector2b = Vector.range(1, 100000) 105 | vector3b = Vector.range(1, 1000000) 106 | 107 | array0 = Array.range(1, 1000) 108 | array1 = Array.range(1, 10000) 109 | array2 = Array.range(1, 100000) 110 | array3 = Array.range(1, 1000000) 111 | array0b = Array.range(1, 1000) 112 | array1b = Array.range(1, 10000) 113 | array2b = Array.range(1, 100000) 114 | array3b = Array.range(1, 1000000) 115 | 116 | arrayC0 = Array.range(1, 1000).map(n => Pair(n, n)) 117 | arrayC1 = Array.range(1, 10000).map(n => Pair(n, n)) 118 | arrayC2 = Array.range(1, 100000).map(n => Pair(n, n)) 119 | arrayC3 = Array.range(1, 1000000).map(n => Pair(n, n)) 120 | arrayC0b = Array.range(1, 1000).map(n => Pair(n, n)) 121 | arrayC1b = Array.range(1, 10000).map(n => Pair(n, n)) 122 | arrayC2b = Array.range(1, 100000).map(n => Pair(n, n)) 123 | arrayC3b = Array.range(1, 1000000).map(n => Pair(n, n)) 124 | 125 | stream0 = Stream.range(1, 1000) 126 | stream1 = Stream.range(1, 10000) 127 | stream2 = Stream.range(1, 100000) 128 | stream3 = Stream.range(1, 1000000) 129 | stream0b = Stream.range(1, 1000) 130 | stream1b = Stream.range(1, 10000) 131 | stream2b = Stream.range(1, 100000) 132 | stream3b = Stream.range(1, 1000000) 133 | 134 | estream0 = EStream.range(1, 1000) 135 | estream1 = EStream.range(1, 10000) 136 | estream2 = EStream.range(1, 100000) 137 | estream3 = EStream.range(1, 1000000) 138 | estream0b = EStream.range(1, 1000) 139 | estream1b = EStream.range(1, 10000) 140 | estream2b = EStream.range(1, 100000) 141 | estream3b = EStream.range(1, 1000000) 142 | } 143 | 144 | def list(a: List[Int], b: List[Int]): List[Int] = a ++ b 145 | def ilist(a: IList[Int], b: IList[Int]): IList[Int] = a ++ b 146 | def vector(a: Vector[Int], b: Vector[Int]): Vector[Int] = a ++ b 147 | def array(a: Array[Int], b: Array[Int]): Array[Int] = a ++ b 148 | def arrayC(a: Array[Pair], b: Array[Pair]): Array[Pair] = a ++ b 149 | def stream(a: Stream[Int], b: Stream[Int]): Stream[Int] = a ++ b 150 | def estream(a: EStream[Int], b: EStream[Int]): EStream[Int] = a ++ b 151 | 152 | @Benchmark 153 | def list1k: List[Int] = list(list0, list0b) 154 | @Benchmark 155 | def list10k: List[Int] = list(list1, list1b) 156 | @Benchmark 157 | def list100k: List[Int] = list(list2, list2b) 158 | @Benchmark 159 | def list1000k: List[Int] = list(list3, list3b) 160 | 161 | @Benchmark 162 | def ilist1k: IList[Int] = ilist(ilist0, ilist0b) 163 | @Benchmark 164 | def ilist10k: IList[Int] = ilist(ilist1, ilist1b) 165 | @Benchmark 166 | def ilist100k: IList[Int] = ilist(ilist2, ilist2b) 167 | @Benchmark 168 | def ilist1000k: IList[Int] = ilist(ilist3, ilist3b) 169 | 170 | @Benchmark 171 | def vector1k: Vector[Int] = vector(vector0 , vector0b) 172 | @Benchmark 173 | def vector10k: Vector[Int] = vector(vector1 , vector1b) 174 | @Benchmark 175 | def vector100k: Vector[Int] = vector(vector2 , vector2b) 176 | @Benchmark 177 | def vector1000k: Vector[Int] = vector(vector3 , vector3b) 178 | 179 | @Benchmark 180 | def array1k: Array[Int] = array(array0 , array0b) 181 | @Benchmark 182 | def array10k: Array[Int] = array(array1 , array1b) 183 | @Benchmark 184 | def array100k: Array[Int] = array(array2 , array2b) 185 | @Benchmark 186 | def array1000k: Array[Int] = array(array3 , array3b) 187 | 188 | @Benchmark 189 | def arrayC1k: Array[Pair] = arrayC(arrayC0 , arrayC0b) 190 | @Benchmark 191 | def arrayC10k: Array[Pair] = arrayC(arrayC1 , arrayC1b) 192 | @Benchmark 193 | def arrayC100k: Array[Pair] = arrayC(arrayC2 , arrayC2b) 194 | @Benchmark 195 | def arrayC1000k: Array[Pair] = arrayC(arrayC3 , arrayC3b) 196 | 197 | @Benchmark 198 | def stream1k: Stream[Int] = stream(stream0 , stream0b) 199 | @Benchmark 200 | def stream10k: Stream[Int] = stream(stream1 , stream1b) 201 | @Benchmark 202 | def stream100k: Stream[Int] = stream(stream2 , stream2b) 203 | @Benchmark 204 | def stream1000k: Stream[Int] = stream(stream3 , stream3b) 205 | 206 | @Benchmark 207 | def estream1k: EStream[Int] = estream(estream0 , estream0b) 208 | @Benchmark 209 | def estream10k: EStream[Int] = estream(estream1 , estream1b) 210 | @Benchmark 211 | def estream100k: EStream[Int] = estream(estream2 , estream2b) 212 | @Benchmark 213 | def estream1000k: EStream[Int] = estream(estream3 , estream3b) 214 | } 215 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/FlattenBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations._ 6 | 7 | // --- // 8 | 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 11 | @State(Scope.Thread) 12 | class FlattenBench { 13 | var smallList: List[Pair] = _ 14 | var bigList: List[Pair] = _ 15 | var hugeList: List[Pair] = _ 16 | var gargList: List[Pair] = _ 17 | 18 | @Setup 19 | def setup(): Unit = { 20 | smallList = (List.range(1, 100) ++ List.range(1, 100)).map(n => Pair(n,n)) 21 | bigList = (List.range(1, 1000) ++ List.range(1, 1000)).map(n => Pair(n,n)) 22 | hugeList = (List.range(1, 10000) ++ List.range(1, 10000)).map(n => Pair(n,n)) 23 | gargList = (List.range(1, 100000) ++ List.range(1, 100000)).map(n => Pair(n,n)) 24 | } 25 | 26 | @Benchmark 27 | def mapFlatten1: List[Pair] = smallList.map { c => List(c) }.flatten 28 | @Benchmark 29 | def mapFlatten2: List[Pair] = bigList.map { c => List(c) }.flatten 30 | @Benchmark 31 | def mapFlatten3: List[Pair] = hugeList.map { c => List(c) }.flatten 32 | @Benchmark 33 | def mapFlatten4: List[Pair] = gargList.map { c => List(c) }.flatten 34 | 35 | @Benchmark 36 | def flatMap1: List[Pair] = smallList.flatMap { c => List(c) } 37 | @Benchmark 38 | def flatMap2: List[Pair] = bigList.flatMap { c => List(c) } 39 | @Benchmark 40 | def flatMap3: List[Pair] = hugeList.flatMap { c => List(c) } 41 | @Benchmark 42 | def flatMap4: List[Pair] = gargList.flatMap { c => List(c) } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/FoldBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import scala.annotation.tailrec 6 | 7 | import org.openjdk.jmh.annotations._ 8 | import scalaz.{IList, ICons, INil, EphemeralStream => EStream} 9 | 10 | // --- // 11 | 12 | @BenchmarkMode(Array(Mode.AverageTime)) 13 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 14 | @State(Scope.Thread) 15 | class FoldBench { 16 | 17 | var list: List[Int] = _ 18 | var ilist: IList[Int] = _ 19 | var vector: Vector[Int] = _ 20 | var array: Array[Int] = _ 21 | var stream: Stream[Int] = _ 22 | var estream: EStream[Int] = _ 23 | 24 | @Setup 25 | def setup: Unit = { 26 | list = List.range(1, 10000) 27 | ilist = IList.fromList(list) 28 | vector = Vector.range(1, 10000) 29 | array = Array.range(1, 10000) 30 | stream = Stream.range(1, 10000) 31 | estream = EStream.range(1, 10000) 32 | } 33 | 34 | @Benchmark 35 | def iterFoldLeft: Int = list.iterator.foldLeft(0)(_ + _) 36 | @Benchmark 37 | def iterFoldRight: Int = list.iterator.foldRight(0)(_ + _) 38 | @Benchmark 39 | def iterWhile: Int = { 40 | var n: Int = 0 41 | val iter: Iterator[Int] = list.iterator 42 | 43 | while (iter.hasNext) { 44 | n += iter.next 45 | } 46 | 47 | n 48 | } 49 | 50 | @Benchmark 51 | def listFoldLeft: Int = list.foldLeft(0)(_ + _) 52 | @Benchmark 53 | def listFoldRight: Int = list.foldRight(0)(_ + _) 54 | @Benchmark 55 | def listTailrec: Int = { 56 | @tailrec def work(l: List[Int], acc: Int): Int = l match { 57 | case h :: rest => work(rest, h + acc) 58 | case Nil => acc 59 | } 60 | 61 | work(list, 0) 62 | } 63 | @Benchmark 64 | def listWhile: Int = { 65 | var i: Int = 0 66 | var l: List[Int] = list 67 | 68 | while (!l.isEmpty) { 69 | i += l.head 70 | l = l.tail 71 | } 72 | 73 | i 74 | } 75 | @Benchmark 76 | def listSum: Int = list.sum 77 | 78 | @Benchmark 79 | def ilistFoldLeft: Int = ilist.foldLeft(0)(_ + _) 80 | @Benchmark 81 | def ilistFoldRight: Int = ilist.foldRight(0)(_ + _) 82 | @Benchmark 83 | def ilistTailrec: Int = { 84 | @tailrec def work(l: IList[Int], acc: Int): Int = l match { 85 | case ICons(h, tail) => work(tail, h + acc) 86 | case INil() => acc 87 | } 88 | 89 | work(ilist, 0) 90 | } 91 | 92 | @Benchmark 93 | def vectorFoldLeft: Int = vector.foldLeft(0)(_ + _) 94 | @Benchmark 95 | def vectorFoldRight: Int = vector.foldRight(0)(_ + _) 96 | @Benchmark 97 | def vectorWhile: Int = { 98 | var n: Int = 0 99 | var i: Int = vector.length - 1 100 | 101 | while (i >= 0) { 102 | n += vector(i) 103 | i -= 1 104 | } 105 | 106 | n 107 | } 108 | @Benchmark 109 | def vectorSum: Int = vector.sum 110 | 111 | @Benchmark 112 | def arrayFoldLeft: Int = array.foldLeft(0)(_ + _) 113 | @Benchmark 114 | def arrayFoldRight: Int = array.foldRight(0)(_ + _) 115 | @Benchmark 116 | def arrayWhile: Int = { 117 | var n: Int = 0 118 | var i: Int = array.length - 1 119 | 120 | while (i >= 0) { 121 | n += array(i) 122 | i -= 1 123 | } 124 | 125 | n 126 | } 127 | @Benchmark 128 | def arraySum: Int = array.sum 129 | 130 | @Benchmark 131 | def streamFoldLeft: Int = stream.foldLeft(0)(_ + _) 132 | @Benchmark 133 | def streamFoldRight: Int = stream.foldRight(0)(_ + _) 134 | @Benchmark 135 | def streamTailrec: Int = { 136 | @tailrec def work(l: Stream[Int], acc: Int): Int = l match { 137 | case _ if l.isEmpty => acc 138 | case h #:: rest => work(rest, h + acc) 139 | } 140 | 141 | work(stream, 0) 142 | } 143 | @Benchmark 144 | def streamWhile: Int = { 145 | var i: Int = 0 146 | var l: Stream[Int] = stream 147 | 148 | while (!l.isEmpty) { 149 | i += l.head 150 | l = l.tail 151 | } 152 | 153 | i 154 | } 155 | @Benchmark 156 | def streamSum: Int = stream.sum 157 | 158 | @Benchmark 159 | def estreamFoldLeft: Int = estream.foldLeft(0) { acc => { n => acc + n } } 160 | @Benchmark 161 | def estreamFoldRight: Int = estream.foldRight(0) { n => { acc => n + acc } } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/FoldClassBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import scala.annotation.tailrec 6 | 7 | import org.openjdk.jmh.annotations._ 8 | import scalaz.{IList, ICons, INil} 9 | 10 | // --- // 11 | 12 | @BenchmarkMode(Array(Mode.AverageTime)) 13 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 14 | @State(Scope.Thread) 15 | class FoldClassBench { 16 | 17 | var list: List[Pair] = _ 18 | var ilist: IList[Pair] = _ 19 | var vector: Vector[Pair] = _ 20 | var array: Array[Pair] = _ 21 | var stream: Stream[Pair] = _ 22 | 23 | @Setup 24 | def setup: Unit = { 25 | list = List.range(1, 10000).map(n => Pair(n, n)) 26 | ilist = IList.fromList(list) 27 | vector = Vector.range(1, 10000).map(n => Pair(n, n)) 28 | array = Array.range(1, 10000).map(n => Pair(n, n)) 29 | stream = Stream.range(1, 10000).map(n => Pair(n, n)) 30 | } 31 | 32 | @Benchmark 33 | def iterFoldLeft: Pair = list.iterator.foldLeft(Pair(0,0))(_ + _) 34 | @Benchmark 35 | def iterFoldRight: Pair = list.iterator.foldRight(Pair(0,0))(_ + _) 36 | @Benchmark 37 | def iterWhile: Pair = { 38 | var n: Pair = Pair(0,0) 39 | val iter: Iterator[Pair] = list.iterator 40 | 41 | while (iter.hasNext) { 42 | n = n + iter.next 43 | } 44 | 45 | n 46 | } 47 | 48 | @Benchmark 49 | def listFoldLeft: Pair = list.foldLeft(Pair(0,0))(_ + _) 50 | @Benchmark 51 | def listFoldRight: Pair = list.foldRight(Pair(0,0))(_ + _) 52 | @Benchmark 53 | def listTailrec: Pair = { 54 | @tailrec def work(l: List[Pair], acc: Pair): Pair = l match { 55 | case Nil => acc 56 | case h :: rest => work(rest, h + acc) 57 | } 58 | 59 | work(list, Pair(0,0)) 60 | } 61 | @Benchmark 62 | def listWhile: Pair = { 63 | var i: Pair = Pair(0,0) 64 | var l: List[Pair] = list 65 | 66 | while (!l.isEmpty) { 67 | i = i + l.head 68 | l = l.tail 69 | } 70 | 71 | i 72 | } 73 | 74 | @Benchmark 75 | def ilistFoldLeft: Pair = ilist.foldLeft(Pair(0,0))(_ + _) 76 | @Benchmark 77 | def ilistFoldRight: Pair = ilist.foldRight(Pair(0,0))(_ + _) 78 | @Benchmark 79 | def ilistTailrec: Pair = { 80 | @tailrec def work(l: IList[Pair], acc: Pair): Pair = l match { 81 | case ICons(h, tail) => work(tail, h + acc) 82 | case INil() => acc 83 | } 84 | 85 | work(ilist, Pair(0,0)) 86 | } 87 | 88 | @Benchmark 89 | def vectorFoldLeft: Pair = vector.foldLeft(Pair(0,0))(_ + _) 90 | @Benchmark 91 | def vectorFoldRight: Pair = vector.foldRight(Pair(0,0))(_ + _) 92 | @Benchmark 93 | def vectorWhile: Pair = { 94 | var n: Pair = Pair(0,0) 95 | var i: Int = vector.length - 1 96 | 97 | while (i >= 0) { 98 | n = n + vector(i) 99 | i -= 1 100 | } 101 | 102 | n 103 | } 104 | 105 | @Benchmark 106 | def arrayFoldLeft: Pair = array.foldLeft(Pair(0,0))(_ + _) 107 | @Benchmark 108 | def arrayFoldRight: Pair = array.foldRight(Pair(0,0))(_ + _) 109 | @Benchmark 110 | def arrayWhile: Pair = { 111 | var n: Pair = Pair(0,0) 112 | var i: Int = array.length - 1 113 | 114 | while (i >= 0) { 115 | n = n + array(i) 116 | i -= 1 117 | } 118 | 119 | n 120 | } 121 | 122 | @Benchmark 123 | def streamFoldLeft: Pair = stream.foldLeft(Pair(0,0))(_ + _) 124 | @Benchmark 125 | def streamFoldRight: Pair = stream.foldRight(Pair(0,0))(_ + _) 126 | @Benchmark 127 | def streamTailrec: Pair = { 128 | @tailrec def work(l: Stream[Pair], acc: Pair): Pair = l match { 129 | case _ if l.isEmpty => acc 130 | case h #:: rest => work(rest, h + acc) 131 | } 132 | 133 | work(stream, Pair(0,0)) 134 | } 135 | @Benchmark 136 | def streamWhile: Pair = { 137 | var i: Pair = Pair(0,0) 138 | var l: Stream[Pair] = stream 139 | 140 | while (!l.isEmpty) { 141 | i = i + l.head 142 | l = l.tail 143 | } 144 | 145 | i 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/MapBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations._ 6 | import scalaz.IList 7 | 8 | // --- // 9 | 10 | @BenchmarkMode(Array(Mode.AverageTime)) 11 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 12 | @State(Scope.Thread) 13 | class MapBench { 14 | 15 | var list0: List[Int] = _ 16 | var list1: List[Int] = _ 17 | var list2: List[Int] = _ 18 | var ilist0: IList[Int] = _ 19 | var ilist1: IList[Int] = _ 20 | var ilist2: IList[Int] = _ 21 | var array0: Array[Int] = _ 22 | var array1: Array[Int] = _ 23 | var array2: Array[Int] = _ 24 | 25 | var listC0: List[Pair] = _ 26 | var listC1: List[Pair] = _ 27 | var listC2: List[Pair] = _ 28 | var ilistC0: IList[Pair] = _ 29 | var ilistC1: IList[Pair] = _ 30 | var ilistC2: IList[Pair] = _ 31 | var arrayC0: Array[Pair] = _ 32 | var arrayC1: Array[Pair] = _ 33 | var arrayC2: Array[Pair] = _ 34 | 35 | @Setup 36 | def setup: Unit = { 37 | list0 = List.range(1, 100) 38 | list1 = List.range(1, 1000) 39 | list2 = List.range(1, 10000) 40 | ilist0 = IList.fromList(list0) 41 | ilist1 = IList.fromList(list1) 42 | ilist2 = IList.fromList(list2) 43 | array0 = Array.range(1, 100) 44 | array1 = Array.range(1, 1000) 45 | array2 = Array.range(1, 10000) 46 | 47 | listC0 = list0.map(n => Pair(n, n)) 48 | listC1 = list1.map(n => Pair(n, n)) 49 | listC2 = list2.map(n => Pair(n, n)) 50 | ilistC0 = ilist0.map(n => Pair(n, n)) 51 | ilistC1 = ilist1.map(n => Pair(n, n)) 52 | ilistC2 = ilist2.map(n => Pair(n, n)) 53 | arrayC0 = array0.map(n => Pair(n, n)) 54 | arrayC1 = array1.map(n => Pair(n, n)) 55 | arrayC2 = array2.map(n => Pair(n, n)) 56 | } 57 | 58 | def arrayWhile(arr: Array[Int]): Array[Int] = { 59 | val newArr: Array[Int] = new Array(arr.length) 60 | var i: Int = 0 61 | 62 | while (i < arr.length) { 63 | newArr(i) = arr(i) * 37 64 | i += 1 65 | } 66 | 67 | newArr 68 | } 69 | 70 | def arrayWhileClass(arr: Array[Pair]): Array[Pair] = { 71 | val newArr: Array[Pair] = new Array(arr.length) 72 | var i: Int = 0 73 | 74 | while (i < arr.length) { 75 | val p: Pair = arr(i) 76 | newArr(i) = Pair(p.col * 37, p.row * 37) 77 | i += 1 78 | } 79 | 80 | newArr 81 | } 82 | 83 | def listMap(l: List[Int]): List[Int] = l.map(_ * 37) 84 | 85 | def listMapClass(l: List[Pair]): List[Pair] = l.map { case Pair(c, r) => Pair(c * 37, r * 37) } 86 | 87 | def ilistMap(l: IList[Int]): IList[Int] = l.map(_ * 37) 88 | 89 | def ilistMapClass(l: IList[Pair]): IList[Pair] = l.map { case Pair(c, r) => Pair(c * 37, r * 37) } 90 | 91 | @Benchmark 92 | def list100: List[Int] = listMap(list0) 93 | @Benchmark 94 | def list1000: List[Int] = listMap(list1) 95 | @Benchmark 96 | def list10000: List[Int] = listMap(list2) 97 | 98 | @Benchmark 99 | def listClass100: List[Pair] = listMapClass(listC0) 100 | @Benchmark 101 | def listClass1000: List[Pair] = listMapClass(listC1) 102 | @Benchmark 103 | def listClass10000: List[Pair] = listMapClass(listC2) 104 | 105 | @Benchmark 106 | def ilist100: IList[Int] = ilistMap(ilist0) 107 | @Benchmark 108 | def ilist1000: IList[Int] = ilistMap(ilist1) 109 | @Benchmark 110 | def ilist10000: IList[Int] = ilistMap(ilist2) 111 | 112 | @Benchmark 113 | def ilistClass100: IList[Pair] = ilistMapClass(ilistC0) 114 | @Benchmark 115 | def ilistClass1000: IList[Pair] = ilistMapClass(ilistC1) 116 | @Benchmark 117 | def ilistClass10000: IList[Pair] = ilistMapClass(ilistC2) 118 | 119 | @Benchmark 120 | def array100: Array[Int] = arrayWhile(array0) 121 | @Benchmark 122 | def array1000: Array[Int] = arrayWhile(array1) 123 | @Benchmark 124 | def array10000: Array[Int] = arrayWhile(array2) 125 | 126 | @Benchmark 127 | def arrayClass100: Array[Pair] = arrayWhileClass(arrayC0) 128 | @Benchmark 129 | def arrayClass1000: Array[Pair] = arrayWhileClass(arrayC1) 130 | @Benchmark 131 | def arrayClass10000: Array[Pair] = arrayWhileClass(arrayC2) 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/MatchBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations._ 6 | 7 | // --- // 8 | 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 11 | @State(Scope.Thread) 12 | class MatchBench { 13 | 14 | val nums: Set[Int] = Set(1,2,3,4,5) 15 | 16 | @Benchmark 17 | def checkWithMatch0: Option[Int] = Nil match { 18 | case _ if nums.contains(6) => Some(6) 19 | case _ => None 20 | } 21 | 22 | @Benchmark 23 | def checkWithMatch1: Option[Int] = Nil match { 24 | case _ if nums.contains(6) => Some(6) 25 | case _ if nums.contains(7) => Some(7) 26 | case _ => None 27 | } 28 | 29 | @Benchmark 30 | def checkWithMatch2: Option[Int] = Nil match { 31 | case _ if nums.contains(6) => Some(6) 32 | case _ if nums.contains(7) => Some(7) 33 | case _ if nums.contains(8) => Some(8) 34 | case _ => None 35 | } 36 | 37 | @Benchmark 38 | def checkWithIfs0: Option[Int] = { 39 | if (nums.contains(6)) { Some(6) } 40 | else { None } 41 | } 42 | 43 | @Benchmark 44 | def checkWithIfs1: Option[Int] = { 45 | if (nums.contains(6)) { Some(6) } 46 | else if (nums.contains(7)) { Some(7) } 47 | else { None } 48 | } 49 | 50 | @Benchmark 51 | def checkWithIfs2: Option[Int] = { 52 | if (nums.contains(6)) { Some(6) } 53 | else if (nums.contains(7)) { Some(7) } 54 | else if (nums.contains(8)) { Some(8) } 55 | else { None } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/MatchContainersBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import scala.annotation.tailrec 6 | 7 | import org.openjdk.jmh.annotations._ 8 | import scalaz.{IList, ICons, INil} 9 | 10 | // --- // 11 | 12 | @BenchmarkMode(Array(Mode.AverageTime)) 13 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 14 | @State(Scope.Thread) 15 | class MatchContainersBench { 16 | 17 | var list: List[Int] = _ 18 | var ilist: IList[Int] = _ 19 | var vec: Vector[Int] = _ 20 | var arr: Array[Int] = _ 21 | var seq: Seq[Int] = _ 22 | var stream: Stream[Int] = _ 23 | 24 | @Setup 25 | def setup: Unit = { 26 | list = List.range(1, 10000) 27 | ilist = IList.fromList(list) 28 | vec = Vector.range(1, 10000) 29 | arr = Array.range(1, 10000) 30 | seq = Seq.range(1, 10000) 31 | stream = Stream.range(1, 10000) 32 | } 33 | 34 | @Benchmark 35 | def lastListMatchCons: Option[Int] = { 36 | @tailrec def work(l: List[Int]): Option[Int] = l match { 37 | case Nil => None 38 | case h :: Nil => Some(h) 39 | case _ :: rest => work(rest) 40 | } 41 | 42 | work(list) 43 | } 44 | 45 | @Benchmark 46 | def lastListMatchGeneric: Option[Int] = { 47 | @tailrec def work(l: List[Int]): Option[Int] = l match { 48 | case Nil => None 49 | case h +: Nil => Some(h) 50 | case _ +: rest => work(rest) 51 | } 52 | 53 | work(list) 54 | } 55 | 56 | /* Unlike Haskell, `tail` on an empty list throws an Exception */ 57 | 58 | @Benchmark 59 | def lastListIf: Option[Int] = { 60 | @tailrec def work(l: List[Int]): Option[Int] = { 61 | if (l.isEmpty) { None } 62 | else { 63 | val t = l.tail 64 | if (t.isEmpty) Some(l.head) else work(t) 65 | } 66 | } 67 | 68 | work(list) 69 | } 70 | 71 | @Benchmark 72 | def lastIListMatchCons: Option[Int] = { 73 | @tailrec def work(l: IList[Int]): Option[Int] = l match { 74 | case INil() => None 75 | case ICons(h, INil()) => Some(h) 76 | case ICons(_, rest) => work(rest) 77 | } 78 | 79 | work(ilist) 80 | } 81 | 82 | @Benchmark 83 | def lastVectorMatchGeneric: Option[Int] = { 84 | @tailrec def work(l: Vector[Int]): Option[Int] = l match { 85 | case Vector() => None 86 | case h +: Vector() => Some(h) 87 | case _ +: rest => work(rest) 88 | } 89 | 90 | work(vec) 91 | } 92 | 93 | @Benchmark 94 | def lastVectorIf: Option[Int] = { 95 | @tailrec def work(l: Vector[Int]): Option[Int] = { 96 | if (l.isEmpty) { None } 97 | else { 98 | val t = l.tail 99 | if (t.isEmpty) Some(l.head) else work(t) 100 | } 101 | } 102 | 103 | work(vec) 104 | } 105 | 106 | @Benchmark 107 | def lastArrayIf: Option[Int] = { 108 | @tailrec def work(l: Array[Int]): Option[Int] = { 109 | if (l.isEmpty) { None } 110 | else { 111 | val t = l.tail 112 | if (t.isEmpty) Some(l.head) else work(t) 113 | } 114 | } 115 | 116 | work(arr) 117 | } 118 | 119 | @Benchmark 120 | def lastSeqMatchGeneric: Option[Int] = { 121 | @tailrec def work(l: Seq[Int]): Option[Int] = l match { 122 | case Seq() => None 123 | case h +: Seq() => Some(h) 124 | case _ +: rest => work(rest) 125 | } 126 | 127 | work(seq) 128 | } 129 | 130 | @Benchmark 131 | def lastSeqIf: Option[Int] = { 132 | @tailrec def work(l: Seq[Int]): Option[Int] = { 133 | if (l.isEmpty) { None } 134 | else { 135 | val t = l.tail 136 | if (t.isEmpty) Some(l.head) else work(t) 137 | } 138 | } 139 | 140 | work(seq) 141 | } 142 | 143 | @Benchmark 144 | def lastStreamMatch: Option[Int] = { 145 | @tailrec def work(l: Stream[Int]): Option[Int] = l match { 146 | case Stream() => None 147 | case h #:: Stream() => Some(h) 148 | case _ #:: rest => work(rest) 149 | } 150 | 151 | work(stream) 152 | } 153 | 154 | @Benchmark 155 | def lastStreamMatchGeneric: Option[Int] = { 156 | @tailrec def work(l: Stream[Int]): Option[Int] = l match { 157 | case Stream() => None 158 | case h +: Stream() => Some(h) 159 | case _ +: rest => work(rest) 160 | } 161 | 162 | work(stream) 163 | } 164 | 165 | @Benchmark 166 | def lastStreamIf: Option[Int] = { 167 | @tailrec def work(l: Stream[Int]): Option[Int] = { 168 | if (l.isEmpty) { None } 169 | else { 170 | val t = l.tail 171 | if (t.isEmpty) Some(l.head) else work(t) 172 | } 173 | } 174 | 175 | work(stream) 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/SetBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations._ 6 | 7 | // --- // 8 | 9 | case class Pair(col: Int, row: Int) { 10 | def +(other: Pair): Pair = Pair(this.col + other.col, this.row + other.row) 11 | } 12 | 13 | @BenchmarkMode(Array(Mode.AverageTime)) 14 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 15 | @State(Scope.Thread) 16 | class SetBench { 17 | var smallList: List[Pair] = _ 18 | var bigList: List[Pair] = _ 19 | var hugeList: List[Pair] = _ 20 | var gargList: List[Pair] = _ 21 | 22 | @Setup 23 | def setup(): Unit = { 24 | smallList = (List.range(1, 100) ++ List.range(1, 100)).map(n => Pair(n,n)) 25 | bigList = (List.range(1, 1000) ++ List.range(1, 1000)).map(n => Pair(n,n)) 26 | hugeList = (List.range(1, 10000) ++ List.range(1, 10000)).map(n => Pair(n,n)) 27 | gargList = (List.range(1, 100000) ++ List.range(1, 100000)).map(n => Pair(n,n)) 28 | } 29 | 30 | @Benchmark 31 | def withDistinct: Iterator[Pair] = smallList.map { case Pair(c,r) => Pair(c + 1, r) }.distinct.iterator 32 | @Benchmark 33 | def withDistinct2: Iterator[Pair] = bigList.map { case Pair(c,r) => Pair(c + 1, r) }.distinct.iterator 34 | @Benchmark 35 | def withDistinct3: Iterator[Pair] = hugeList.map { case Pair(c,r) => Pair(c + 1, r) }.distinct.iterator 36 | @Benchmark 37 | def withDistinct4: Iterator[Pair] = gargList.map { case Pair(c,r) => Pair(c + 1, r) }.distinct.iterator 38 | 39 | @Benchmark 40 | def withSet: Iterator[Pair] = smallList.map { case Pair(c,r) => Pair(c + 1, r) }.toSet.iterator 41 | @Benchmark 42 | def withSet2: Iterator[Pair] = bigList.map { case Pair(c,r) => Pair(c + 1, r) }.toSet.iterator 43 | @Benchmark 44 | def withSet3: Iterator[Pair] = hugeList.map { case Pair(c,r) => Pair(c + 1, r) }.toSet.iterator 45 | @Benchmark 46 | def withSet4: Iterator[Pair] = gargList.map { case Pair(c,r) => Pair(c + 1, r) }.toSet.iterator 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/SortBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations._ 6 | 7 | // --- // 8 | 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 11 | @State(Scope.Thread) 12 | class SortBench { 13 | 14 | var list0: List[Int] = _ 15 | var list1: List[Int] = _ 16 | var list2: List[Int] = _ 17 | var vec0: Vector[Int] = _ 18 | var vec1: Vector[Int] = _ 19 | var vec2: Vector[Int] = _ 20 | var arr0: Array[Int] = _ 21 | var arr1: Array[Int] = _ 22 | var arr2: Array[Int] = _ 23 | 24 | @Setup 25 | def setup: Unit = { 26 | list0 = List.range(1, 1000) 27 | list1 = List.range(1, 10000) 28 | list2 = List.range(1, 100000) 29 | vec0 = Vector.range(1, 1000) 30 | vec1 = Vector.range(1, 10000) 31 | vec2 = Vector.range(1, 100000) 32 | arr0 = Array.range(1, 1000) 33 | arr1 = Array.range(1, 10000) 34 | arr2 = Array.range(1, 100000) 35 | } 36 | 37 | @Benchmark 38 | def sortStream0: Stream[Int] = Stream.range(1, 1000).sorted 39 | @Benchmark 40 | def sortStream1: Stream[Int] = Stream.range(1, 10000).sorted 41 | @Benchmark 42 | def sortStream2: Stream[Int] = Stream.range(1, 100000).sorted 43 | 44 | @Benchmark 45 | def sortList0: List[Int] = list0.sorted 46 | @Benchmark 47 | def sortList1: List[Int] = list1.sorted 48 | @Benchmark 49 | def sortList2: List[Int] = list2.sorted 50 | 51 | @Benchmark 52 | def sortVector0: Vector[Int] = vec0.sorted 53 | @Benchmark 54 | def sortVector1: Vector[Int] = vec1.sorted 55 | @Benchmark 56 | def sortVector2: Vector[Int] = vec2.sorted 57 | 58 | @Benchmark 59 | def sortArray0: Array[Int] = arr0.sorted 60 | @Benchmark 61 | def sortArray1: Array[Int] = arr1.sorted 62 | @Benchmark 63 | def sortArray2: Array[Int] = arr2.sorted 64 | 65 | } 66 | 67 | @BenchmarkMode(Array(Mode.AverageTime)) 68 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 69 | @State(Scope.Thread) 70 | class BackwardSortBench { 71 | 72 | var list0: List[Int] = _ 73 | var list1: List[Int] = _ 74 | var list2: List[Int] = _ 75 | var vec0: Vector[Int] = _ 76 | var vec1: Vector[Int] = _ 77 | var vec2: Vector[Int] = _ 78 | var arr0: Array[Int] = _ 79 | var arr1: Array[Int] = _ 80 | var arr2: Array[Int] = _ 81 | 82 | @Setup 83 | def setup: Unit = { 84 | list0 = List.range(1000, 1, -1) 85 | list1 = List.range(10000, 1, -1) 86 | list2 = List.range(100000, 1, -1) 87 | vec0 = Vector.range(1000, 1, -1) 88 | vec1 = Vector.range(10000, 1, -1) 89 | vec2 = Vector.range(100000, 1, -1) 90 | arr0 = Array.range(1000, 1, -1) 91 | arr1 = Array.range(10000, 1, -1) 92 | arr2 = Array.range(100000, 1, -1) 93 | } 94 | 95 | @Benchmark 96 | def sortStream0: Stream[Int] = Stream.range(1000, 1, -1).sorted 97 | @Benchmark 98 | def sortStream1: Stream[Int] = Stream.range(10000, 1, -1).sorted 99 | @Benchmark 100 | def sortStream2: Stream[Int] = Stream.range(100000, 1, -1).sorted 101 | 102 | @Benchmark 103 | def sortList0: List[Int] = list0.sorted 104 | @Benchmark 105 | def sortList1: List[Int] = list1.sorted 106 | @Benchmark 107 | def sortList2: List[Int] = list2.sorted 108 | 109 | @Benchmark 110 | def sortVector0: Vector[Int] = vec0.sorted 111 | @Benchmark 112 | def sortVector1: Vector[Int] = vec1.sorted 113 | @Benchmark 114 | def sortVector2: Vector[Int] = vec2.sorted 115 | 116 | @Benchmark 117 | def sortArray0: Array[Int] = arr0.sorted 118 | @Benchmark 119 | def sortArray1: Array[Int] = arr1.sorted 120 | @Benchmark 121 | def sortArray2: Array[Int] = arr2.sorted 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/StreamBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations._ 6 | import scalaz.{IList, EphemeralStream} 7 | import scalaz.Scalaz._ 8 | 9 | // --- // 10 | 11 | @BenchmarkMode(Array(Mode.AverageTime)) 12 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 13 | @State(Scope.Thread) 14 | class StreamBench { 15 | 16 | var list1: List[Int] = _ 17 | var list2: List[Int] = _ 18 | var ilist1: IList[Int] = _ 19 | var ilist2: IList[Int] = _ 20 | var vec1: Vector[Int] = _ 21 | var vec2: Vector[Int] = _ 22 | var arr1: Array[Int] = _ 23 | var arr2: Array[Int] = _ 24 | var str1: Stream[Int] = _ 25 | var str2: Stream[Int] = _ 26 | var estr1: EphemeralStream[Int] = _ 27 | var estr2: EphemeralStream[Int] = _ 28 | 29 | @Setup 30 | def setup: Unit = { 31 | list1 = List.range(1, 10000) 32 | list2 = List.range(10000, 1, -1) 33 | ilist1 = IList.fromList(list1) 34 | ilist2 = IList.fromList(list2) 35 | vec1 = Vector.range(1, 10000) 36 | vec2 = Vector.range(10000, 1, -1) 37 | arr1 = Array.range(1, 10000) 38 | arr2 = Array.range(10000, 1, -1) 39 | str1 = Stream.range(1, 10000) 40 | str2 = Stream.range(10000, 1, -1) 41 | estr1 = EphemeralStream.range(1, 10000) 42 | estr2 = EphemeralStream.fromStream(str2) 43 | } 44 | 45 | @Benchmark 46 | def streamMax: Int = str1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).max 47 | @Benchmark 48 | def streamHead: Int = str1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).head 49 | @Benchmark 50 | def streamReverse: Int = str1.reverse.head 51 | @Benchmark 52 | def streamSort: Int = str2.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).sorted.head 53 | 54 | @Benchmark 55 | def ephemeralStreamMax: Option[Int] = estr1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).maximum 56 | @Benchmark 57 | def ephemeralStreamHead: Option[Int] = estr1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).headOption 58 | @Benchmark 59 | def ephemeralStreamReverse: Option[Int] = estr1.reverse.headOption 60 | 61 | @Benchmark 62 | def iterMax: Int = list1.iterator.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).max 63 | @Benchmark 64 | def iterHead: Int = list1.iterator.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).next 65 | 66 | @Benchmark 67 | def listMax: Int = list1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).max 68 | @Benchmark 69 | def listHead: Int = list1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).head 70 | @Benchmark 71 | def listReverse: Int = list1.reverse.head 72 | @Benchmark 73 | def listSort: Int = list2.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).sorted.head 74 | 75 | @Benchmark 76 | def ilistMax: Option[Int] = ilist1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).maximum 77 | @Benchmark 78 | def ilistHead: Option[Int] = ilist1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).headOption 79 | @Benchmark 80 | def ilistReverse: Option[Int] = ilist1.reverse.headOption 81 | @Benchmark 82 | def ilistSort: Option[Int] = ilist2.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).sorted.headOption 83 | 84 | @Benchmark 85 | def vectorMax: Int = vec1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).max 86 | @Benchmark 87 | def vectorHead: Int = vec1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).head 88 | @Benchmark 89 | def vectorReverse: Int = vec1.reverse.head 90 | @Benchmark 91 | def vectorSort: Int = vec2.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).sorted.head 92 | 93 | @Benchmark 94 | def arrayMax: Int = arr1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).max 95 | @Benchmark 96 | def arrayHead: Int = arr1.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).head 97 | @Benchmark 98 | def arrayReverse: Int = arr1.reverse.head 99 | @Benchmark 100 | def arraySort: Int = arr2.map(_ + 1).filter(_ % 2 == 0).map(_ * 2).sorted.head 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/UniquesBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.{ TimeUnit, ConcurrentHashMap } 4 | 5 | import scala.collection.mutable.{ Set => MSet } 6 | 7 | import org.openjdk.jmh.annotations._ 8 | 9 | // --- // 10 | 11 | @BenchmarkMode(Array(Mode.AverageTime)) 12 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 13 | @State(Scope.Thread) 14 | class UniquesBench { 15 | 16 | var arr0: Array[Int] = _ 17 | var arr1: Array[Int] = _ 18 | var arr2: Array[Int] = _ 19 | 20 | @Setup 21 | def setup: Unit = { 22 | arr0 = Array.range(0, 50) ++ Array.range(0, 50) 23 | arr1 = Array.range(0, 500) ++ Array.range(0, 500) 24 | arr2 = Array.range(0, 5000) ++ Array.range(0, 5000) 25 | } 26 | 27 | @Benchmark 28 | def hashmap100: ConcurrentHashMap[(Int, Int), Unit] = hashmap(arr0) 29 | @Benchmark 30 | def hashmap1000: ConcurrentHashMap[(Int, Int), Unit] = hashmap(arr1) 31 | @Benchmark 32 | def hashmap10000: ConcurrentHashMap[(Int, Int), Unit] = hashmap(arr2) 33 | 34 | def hashmap(arr: Array[Int]): ConcurrentHashMap[(Int, Int), Unit] = { 35 | val c = new ConcurrentHashMap[(Int, Int), Unit] 36 | var i: Int = 0 37 | val len: Int = arr.length 38 | 39 | while (i < len) { 40 | val n = arr(i) 41 | 42 | c.put((n, n), ()) 43 | i += 1 44 | } 45 | 46 | c 47 | } 48 | 49 | @Benchmark 50 | def set100: Set[(Int, Int)] = set(arr0) 51 | @Benchmark 52 | def set1000: Set[(Int, Int)] = set(arr1) 53 | @Benchmark 54 | def set10000: Set[(Int, Int)] = set(arr2) 55 | 56 | def set(arr: Array[Int]): Set[(Int, Int)] = { 57 | var s: Set[(Int, Int)] = Set.empty 58 | var i: Int = 0 59 | val len: Int = arr.length 60 | 61 | while (i < len) { 62 | val n = arr(i) 63 | 64 | s = s + ((n, n)) 65 | i += 1 66 | } 67 | 68 | s 69 | } 70 | 71 | @Benchmark 72 | def mset100: MSet[(Int, Int)] = mset(arr0) 73 | @Benchmark 74 | def mset1000: MSet[(Int, Int)] = mset(arr1) 75 | @Benchmark 76 | def mset10000: MSet[(Int, Int)] = mset(arr2) 77 | 78 | def mset(arr: Array[Int]): MSet[(Int, Int)] = { 79 | val s: MSet[(Int, Int)] = MSet.empty 80 | var i: Int = 0 81 | val len: Int = arr.length 82 | 83 | while (i < len) { 84 | val n = arr(i) 85 | 86 | s += ((n, n)) 87 | i += 1 88 | } 89 | 90 | s 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/scala/benchmarks/VectorBench.scala: -------------------------------------------------------------------------------- 1 | package benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import scala.annotation.tailrec 6 | import scala.collection.immutable.VectorBuilder 7 | import scala.collection.mutable.{ArrayBuilder, ListBuffer} 8 | 9 | import org.openjdk.jmh.annotations._ 10 | import scalaz.{IList, ICons, INil} 11 | 12 | // --- // 13 | 14 | @BenchmarkMode(Array(Mode.AverageTime)) 15 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 16 | @State(Scope.Thread) 17 | class VectorBench { 18 | 19 | def array(n: Int): Array[Int] = { 20 | val a: Array[Int] = new Array(n) 21 | var i: Int = 0 22 | 23 | while (i < n) { 24 | a(i) = i 25 | i += 1 26 | } 27 | 28 | a 29 | } 30 | 31 | def abuilder(n: Int): Array[Int] = { 32 | val b = new ArrayBuilder.ofInt 33 | var i: Int = 0 34 | 35 | /* Cheat and tell the builder how big you expect the result to be. 36 | * Introduces a ~2x slowdown vs Array. 37 | * 38 | * Underestimating the hint (say by 2) will introduce about a 4x 39 | * slowdown. Not hinting at all is about 5x slower overall. 40 | * 41 | * Rule of thumb: if you hint, try to overshoot. 42 | */ 43 | b.sizeHint(n * 2) 44 | 45 | while (i < n) { 46 | b += i 47 | i += 1 48 | } 49 | 50 | b.result 51 | } 52 | 53 | def vector(n: Int): Vector[Int] = { 54 | val v = Vector.empty[Int] 55 | var i: Int = 0 56 | 57 | while (i < n) { 58 | i +: v 59 | i += 1 60 | } 61 | 62 | v 63 | } 64 | 65 | def vbuilder(n: Int): Vector[Int] = { 66 | val b = new VectorBuilder[Int] 67 | var i: Int = 0 68 | 69 | while (i < n) { 70 | b += i 71 | i += 1 72 | } 73 | 74 | b.result 75 | } 76 | 77 | def list(n: Int): List[Int] = { 78 | 79 | @tailrec def work(acc: List[Int], m: Int): List[Int] = m match { 80 | case _ if m == n => acc 81 | case _ => work(m :: acc, m + 1) 82 | } 83 | 84 | work(Nil, 0) 85 | } 86 | 87 | def ilist(n: Int): IList[Int] = { 88 | 89 | @tailrec def work(acc: IList[Int], m: Int): IList[Int] = m match { 90 | case _ if m == n => acc 91 | case _ => work(ICons(m, acc), m + 1) 92 | } 93 | 94 | work(INil(), 0) 95 | 96 | } 97 | 98 | def buffer(n: Int): List[Int] = { 99 | val b = new ListBuffer[Int] 100 | var i: Int = 0 101 | 102 | while (i < n) { 103 | i +=: b 104 | i += 1 105 | } 106 | 107 | b.result 108 | } 109 | 110 | def foryield(n: Int): IndexedSeq[Int] = for (i <- 0 to n) yield i 111 | 112 | @Benchmark 113 | def array1000: Array[Int] = array(1000) 114 | @Benchmark 115 | def array10000: Array[Int] = array(10000) 116 | @Benchmark 117 | def array100000: Array[Int] = array(100000) 118 | 119 | @Benchmark 120 | def abuilder1000: Array[Int] = abuilder(1000) 121 | @Benchmark 122 | def abuilder10000: Array[Int] = abuilder(10000) 123 | @Benchmark 124 | def abuilder100000: Array[Int] = abuilder(100000) 125 | 126 | @Benchmark 127 | def vector1000: Vector[Int] = vector(1000) 128 | @Benchmark 129 | def vector10000: Vector[Int] = vector(10000) 130 | @Benchmark 131 | def vector100000: Vector[Int] = vector(100000) 132 | 133 | @Benchmark 134 | def vbuilder1000: Vector[Int] = vbuilder(1000) 135 | @Benchmark 136 | def vbuilder10000: Vector[Int] = vbuilder(10000) 137 | @Benchmark 138 | def vbuilder100000: Vector[Int] = vbuilder(100000) 139 | 140 | @Benchmark 141 | def list1000: List[Int] = list(1000) 142 | @Benchmark 143 | def list10000: List[Int] = list(10000) 144 | @Benchmark 145 | def list100000: List[Int] = list(100000) 146 | 147 | @Benchmark 148 | def ilist1000: IList[Int] = ilist(1000) 149 | @Benchmark 150 | def ilist10000: IList[Int] = ilist(10000) 151 | @Benchmark 152 | def ilist100000: IList[Int] = ilist(100000) 153 | 154 | @Benchmark 155 | def buffer1000: List[Int] = buffer(1000) 156 | @Benchmark 157 | def buffer10000: List[Int] = buffer(10000) 158 | @Benchmark 159 | def buffer100000: List[Int] = buffer(100000) 160 | 161 | @Benchmark 162 | def foryield1000: IndexedSeq[Int] = foryield(1000) 163 | @Benchmark 164 | def foryield10000: IndexedSeq[Int] = foryield(10000) 165 | @Benchmark 166 | def foryield100000: IndexedSeq[Int] = foryield(100000) 167 | 168 | } 169 | --------------------------------------------------------------------------------