├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build.sbt ├── chapter2 ├── src │ ├── main │ │ ├── resources │ │ │ └── historical_data │ │ └── scala │ │ │ └── highperfscala │ │ │ ├── benchmarks │ │ │ ├── BatchExample.scala │ │ │ ├── CancelBenchmarks.scala │ │ │ ├── FinalLatencyBenchmark.scala │ │ │ ├── FirstLatencyBenchmark.scala │ │ │ ├── IoExample.scala │ │ │ ├── PseudoBenchmark.scala │ │ │ ├── SecondLatencyBenchmark.scala │ │ │ ├── ThroughputBenchmark.scala │ │ │ └── util │ │ │ │ ├── DataCodec.scala │ │ │ │ ├── DataGenerator.scala │ │ │ │ └── package.scala │ │ │ └── orderbook │ │ │ ├── OrderBook.scala │ │ │ └── model.scala │ └── test │ │ └── scala │ │ └── highperfscala │ │ └── orderbook │ │ ├── OrderBookCancelingSpec.scala │ │ └── OrderBookTradingSpec.scala └── target │ ├── .history │ └── streams │ └── $global │ └── clean │ └── $global │ └── streams │ └── out ├── chapter3 ├── src │ └── main │ │ └── scala │ │ └── highperfscala │ │ ├── anyval │ │ ├── TaggedTypes.scala │ │ └── ValueClasses.scala │ │ ├── option │ │ ├── NullOptionApp.scala │ │ ├── OptionBenchmarks.scala │ │ ├── OptionCreationBenchmarks.scala │ │ ├── OptionFoldingBenchmarks.scala │ │ ├── OptionzTest.scala │ │ ├── Program.scala │ │ ├── ScalaOptionExample.scala │ │ ├── abstractAdt.scala │ │ └── opt.scala │ │ ├── patternmatch │ │ ├── PatternMatching.scala │ │ └── PatternMatchingBenchmarks.scala │ │ ├── specialization │ │ ├── Inheritance.scala │ │ ├── MethodReturnTypes.scala │ │ ├── Specialization.scala │ │ ├── SpecializationBenchmark.scala │ │ └── UnexpectedAllocations.scala │ │ └── tailrec │ │ └── TailRecursion.scala └── target │ ├── .history │ └── streams │ └── $global │ ├── clean │ └── $global │ │ └── streams │ │ └── out │ ├── ivyConfiguration │ └── $global │ │ └── streams │ │ └── out │ ├── ivySbt │ └── $global │ │ └── streams │ │ └── out │ └── projectDescriptors │ └── $global │ └── streams │ └── out ├── chapter4 ├── src │ ├── main │ │ ├── resources │ │ │ └── dataanalysis │ │ │ │ └── executions │ │ └── scala │ │ │ └── highperfscala │ │ │ ├── dataanalysis │ │ │ ├── ListVectorExperiment.scala │ │ │ ├── MidpointSeries.scala │ │ │ ├── ReturnSeriesFrame.scala │ │ │ ├── api.scala │ │ │ ├── benchmarks │ │ │ │ ├── ListVectorBenchmarks.scala │ │ │ │ └── ReturnSeriesFrameBenchmarks.scala │ │ │ └── util │ │ │ │ ├── DataCodec.scala │ │ │ │ └── DataGenerator.scala │ │ │ ├── features │ │ │ └── FeatureGeneration.scala │ │ │ └── orderbook │ │ │ ├── CancelBenchmarks.scala │ │ │ ├── InterleavedOrderBenchmarks.scala │ │ │ ├── LatencyBenchmark.scala │ │ │ ├── LazyCancelOrderBook.scala │ │ │ ├── ListOrderBook.scala │ │ │ ├── QueueOrderBook.scala │ │ │ ├── api.scala │ │ │ ├── model.scala │ │ │ └── util │ │ │ ├── DataCodec.scala │ │ │ └── package.scala │ └── test │ │ └── scala │ │ └── highperfscala │ │ ├── dataanalysis │ │ └── MidpointSeriesSpec.scala │ │ └── orderbook │ │ ├── LazyCancelOrderBookCancelingSpec.scala │ │ ├── LazyCancelOrderBookTradingSpec.scala │ │ ├── ListOrderBookCancelingSpec.scala │ │ ├── ListOrderBookTradingSpec.scala │ │ ├── QueueOrderBookCancelingSpec.scala │ │ └── QueueOrderBookTradingSpec.scala └── target │ ├── .history │ └── streams │ └── $global │ ├── clean │ └── $global │ │ └── streams │ │ └── out │ ├── ivyConfiguration │ └── $global │ │ └── streams │ │ └── out │ └── ivySbt │ └── $global │ └── streams │ └── out ├── chapter5 ├── src │ └── main │ │ └── scala │ │ └── highperfscala │ │ └── clientreports │ │ ├── streams │ │ ├── EventSourcing.scala │ │ ├── MarkovChainEventGenerator.scala │ │ ├── StreamsExamples.scala │ │ └── model.scala │ │ └── views │ │ ├── PerformanceReporting.scala │ │ ├── ViewBenchmarks.scala │ │ ├── ViewDemo.scala │ │ ├── ViewPerformanceReporting.scala │ │ ├── api.scala │ │ └── pseudoView.scala └── target │ ├── .history │ └── streams │ └── $global │ ├── clean │ └── $global │ │ └── streams │ │ └── out │ ├── ivyConfiguration │ └── $global │ │ └── streams │ │ └── out │ ├── ivySbt │ └── $global │ │ └── streams │ │ └── out │ └── projectDescriptors │ └── $global │ └── streams │ └── out ├── chapter6 ├── src │ └── main │ │ └── scala │ │ └── highperfscala │ │ └── concurrency │ │ ├── backtesting │ │ ├── Backtest.scala │ │ ├── BacktestBenchmarks.scala │ │ └── Backtesting.scala │ │ ├── blocking │ │ ├── BlockingExample.scala │ │ └── BlockingFutureBenchmarks.scala │ │ ├── future │ │ ├── FutureErrorHandling.scala │ │ ├── FutureExample.scala │ │ ├── OrderSubmission.scala │ │ ├── SafeAwait.scala │ │ └── TransformFutureBenchmarks.scala │ │ └── task │ │ ├── TaskExample.scala │ │ └── TaskFutureBenchmarks.scala └── target │ ├── .history │ └── streams │ └── $global │ └── clean │ └── $global │ └── streams │ └── out ├── chapter7 ├── src │ └── main │ │ └── scala │ │ └── highperfscala │ │ ├── crdt │ │ ├── Counters.scala │ │ └── sets.scala │ │ └── free │ │ ├── BboUpdatedBenchmark.scala │ │ ├── BboUpdatedPipeline.scala │ │ ├── EitherFree.scala │ │ ├── GenericBenchmark.scala │ │ ├── ProcessingLatencyMs.scala │ │ ├── dsl.scala │ │ ├── model.scala │ │ ├── thunk.scala │ │ └── tradingStrategies.scala └── target │ └── streams │ └── $global │ ├── clean │ └── $global │ │ └── streams │ │ └── out │ ├── ivyConfiguration │ └── $global │ │ └── streams │ │ └── out │ ├── ivySbt │ └── $global │ │ └── streams │ │ └── out │ └── projectDescriptors │ └── $global │ └── streams │ └── out └── project ├── build.properties └── plugins.sbt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Packt Publishing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scala-High-Performance-Programming 2 | 3 | ##### Code repository for [Scala-High-Performance-Programming](https://www.packtpub.com/application-development/scala-high-performance-programming?utm_source=GitHub&utm_medium=repo&utm_campaign=9781786466044), published by [PACKT Publishing](https://www.packtpub.com) 4 | 5 | Scala is an audacious programming language that blends object-oriented and functional programming concepts on the JVM. 6 | You should install Java Development Kit version 8 or higher for your operating system to work through all code examples. This book discusses the Oracle HotSpot JVM, and it demonstrates tools that are included in Oracle JDK. You should also get the latest version of [sbt](http://www.scala-sbt.org/download.html) (version 0.13.11, at the time of writing). 7 | 8 | 9 | #### Additionally you can refer to the following books: 10 | * [Scala for Java Developers](https://www.packtpub.com/application-development/scala-java-developers?utm_source=GitHub&utm_medium=repo&utm_campaign=9781783283637) 11 | * [Scala for Machine Learning](https://www.packtpub.com/big-data-and-business-intelligence/scala-machine-learning?utm_source=GitHub&utm_medium=repo&utm_campaign=9781783558742) 12 | * [Learning Concurrent Programming in Scala](https://www.packtpub.com/application-development/learning-concurrent-programming-scala?utm_source=GitHub&utm_medium=repo&utm_campaign=9781783281411) 13 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | 2 | name := "highperfscala" 3 | organization := "highperfscala" 4 | 5 | val scalazVersion = "7.2.0" 6 | val specsVersion = "3.7.3" 7 | 8 | val slf4s = "org.slf4s" %% "slf4s-api" % "1.7.12" 9 | val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.13.0" 10 | val hdrHistogram = "org.mpierce.metrics.reservoir" % 11 | "hdrhistogram-metrics-reservoir" % "1.1.0" 12 | val scalaz = "org.scalaz" %% "scalaz-core" % scalazVersion 13 | val scalazConcurrent = "org.scalaz" %% "scalaz-concurrent" % scalazVersion 14 | val specs2 = "org.specs2" %% "specs2-core" % specsVersion 15 | val specs2ScalaCheck = "org.specs2" %% "specs2-scalacheck" % specsVersion 16 | val joda = "joda-time" % "joda-time" % "2.8.2" 17 | val jodaConvert = "org.joda" % "joda-convert" % "1.8" 18 | val breeze = "org.scalanlp" %% "breeze" % "0.11.2" exclude( 19 | "org.spire-math", "spire_2.11") 20 | val spire = "org.spire-math" %% "spire" % "0.11.0" 21 | val spireMacro = "org.spire-math" %% "spire-macros" % "0.11.0" 22 | val saddle = "org.scala-saddle" %% "saddle-core" % "1.3.4" 23 | 24 | 25 | val baseOptions = Defaults.coreDefaultSettings ++ Seq( 26 | scalaVersion := "2.11.8", 27 | fork := true, 28 | resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 29 | libraryDependencies ++= List( 30 | slf4s, scalaCheck, hdrHistogram, scalaz, joda, jodaConvert, 31 | specs2 % "test", specs2ScalaCheck % "test" 32 | ) 33 | ) 34 | 35 | lazy val root = Project( 36 | id = "highperfscala", 37 | base = file("."), 38 | settings = baseOptions ++ Seq( 39 | onLoadMessage ~= (_ + (if (sys.props("java.specification.version") != "1.8") { 40 | """ 41 | |You seem to not be running Java 1.8. 42 | |While the provided code may still work, we recommend that you 43 | |upgrade your version of Java. 44 | """.stripMargin 45 | } else "")))) aggregate( 46 | chapter2, chapter3, chapter4, chapter5, chapter6, chapter7) 47 | 48 | lazy val chapter2 = Project( 49 | id = "chapter2", 50 | base = file("chapter2"), 51 | settings = baseOptions 52 | ).enablePlugins(JmhPlugin) 53 | 54 | lazy val chapter3 = Project( 55 | id = "chapter3", 56 | base = file("chapter3"), 57 | settings = baseOptions 58 | ).enablePlugins(JmhPlugin) 59 | 60 | lazy val chapter4 = Project( 61 | id = "chapter4", 62 | base = file("chapter4"), 63 | settings = baseOptions 64 | ).settings( 65 | libraryDependencies ++= Seq(breeze, spire, spireMacro, saddle) 66 | ).enablePlugins(JmhPlugin) 67 | 68 | lazy val chapter5 = Project( 69 | id = "chapter5", 70 | base = file("chapter5"), 71 | settings = baseOptions 72 | ).enablePlugins(JmhPlugin) 73 | 74 | lazy val chapter6 = Project( 75 | id = "chapter6", 76 | base = file("chapter6"), 77 | settings = baseOptions 78 | ).settings( 79 | libraryDependencies ++= Seq(scalazConcurrent) 80 | ).enablePlugins(JmhPlugin) 81 | 82 | lazy val chapter7 = Project( 83 | id = "chapter7", 84 | base = file("chapter7"), 85 | settings = baseOptions 86 | ).settings( 87 | libraryDependencies ++= Seq(scalazConcurrent) 88 | ).enablePlugins(JmhPlugin) 89 | -------------------------------------------------------------------------------- /chapter2/src/main/resources/historical_data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter2/src/main/resources/historical_data -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/BatchExample.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | 4 | import highperfscala.benchmarks.BatchExample._ 5 | import org.openjdk.jmh.annotations._ 6 | 7 | // Scala transcription of 8 | // http://hg.openjdk.java.net/code-tools/jmh/file/bcec9a03787f/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_26_BatchSize.java 9 | // 10 | // Run via 11 | // sbt 'project performance;jmh:run .*BatchExample -f 1 -jvmArgs "-Xmx1G -Xms1G"' 12 | @State(Scope.Thread) 13 | class BatchExample { 14 | @Benchmark 15 | @Warmup(iterations = 5, time = 1) 16 | @Measurement(iterations = 5, time = 1) 17 | @BenchmarkMode(Array(Mode.Throughput)) 18 | def measureWrong_1(): java.util.List[String] = { 19 | xs.add(xs.size() / 2, "something") 20 | xs 21 | } 22 | 23 | @Benchmark 24 | @Warmup(iterations = 5, time = 5) 25 | @Measurement(iterations = 5, time = 5) 26 | @BenchmarkMode(Array(Mode.Throughput)) 27 | def measureWrong_5(): java.util.List[String] = { 28 | xs.add(xs.size() / 2, "something") 29 | xs 30 | } 31 | 32 | @Benchmark 33 | @Warmup(iterations = 5, batchSize = 5000) 34 | @Measurement(iterations = 5, batchSize = 5000) 35 | @BenchmarkMode(Array(Mode.SingleShotTime)) 36 | def measureRight(): java.util.List[String] = { 37 | xs.add(xs.size() / 2, "something") 38 | xs 39 | } 40 | 41 | @Setup(Level.Iteration) 42 | def setup(): Unit = xs.clear() 43 | } 44 | 45 | object BatchExample { 46 | val xs = new java.util.LinkedList[String]() 47 | } 48 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/CancelBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | 4 | import java.util.concurrent.TimeUnit 5 | 6 | import highperfscala.benchmarks.CancelBenchmarks._ 7 | import highperfscala.orderbook.Commands._ 8 | import highperfscala.orderbook.Events.Event 9 | import highperfscala.orderbook.{BuyLimitOrder, OrderId, Price, OrderBook} 10 | import org.openjdk.jmh.annotations._ 11 | import org.openjdk.jmh.annotations.Mode._ 12 | 13 | // Run via 14 | // sbt ';project performance;jmh:run .*CancelBenchmarks -wi 3 -i 10 -f 1 -wbs 100000 -bs 100000 -jvmArgs "-Xmx1G -Xms1G" -foe true -p enqueuedOrderCount=10,100,1000,5000' 15 | @BenchmarkMode(Array(Throughput)) 16 | @OutputTimeUnit(TimeUnit.SECONDS) 17 | class CancelBenchmarks { 18 | 19 | @Benchmark 20 | def cancelLastOrderInLine(b: BookWithLargeQueue): (OrderBook, Event) = 21 | OrderBook.handle(b.book, b.cancelLast) 22 | 23 | @Benchmark 24 | def cancelFirstOrderInLine(b: BookWithLargeQueue): (OrderBook, Event) = 25 | OrderBook.handle(b.book, b.cancelFirst) 26 | 27 | @Benchmark 28 | def cancelNonexistentOrder(b: BookWithLargeQueue): (OrderBook, Event) = 29 | OrderBook.handle(b.book, b.cancelNonexistent) 30 | } 31 | 32 | object CancelBenchmarks { 33 | 34 | @State(Scope.Benchmark) 35 | class BookWithLargeQueue { 36 | private val p = Price(BigDecimal(1.00)) 37 | private val firstId: Int = 1 38 | private val defaultCancelLast = CancelOrder(OrderId(-1)) 39 | 40 | @Param(Array("1", "100", "1000")) 41 | var enqueuedOrderCount: Int = 0 42 | 43 | var book: OrderBook = OrderBook.empty 44 | 45 | @Setup(Level.Trial) 46 | def setup(): Unit = { 47 | if (enqueuedOrderCount < 0) 48 | sys.error(s"Invalid enqueued order count = $enqueuedOrderCount") 49 | assert(book == OrderBook.empty) 50 | assert(cancelLast == defaultCancelLast) 51 | 52 | cancelLast = CancelOrder(OrderId(enqueuedOrderCount)) 53 | book = { 54 | (firstId to enqueuedOrderCount).foldLeft(OrderBook.empty) { 55 | case (ob, i) => 56 | OrderBook.handle(ob, AddLimitOrder(BuyLimitOrder(OrderId(i), p)))._1 57 | } 58 | } 59 | 60 | assert(cancelLast != defaultCancelLast) 61 | if (enqueuedOrderCount > 0) 62 | assert(book.bids.head._2.size == enqueuedOrderCount, 63 | s"Book built incorrectly! Expected book to contain " + 64 | s"$enqueuedOrderCount bids for $p, but actual book is $book") 65 | } 66 | 67 | var cancelLast: CancelOrder = defaultCancelLast 68 | val cancelFirst: CancelOrder = CancelOrder(OrderId(firstId)) 69 | val cancelNonexistent: CancelOrder = CancelOrder(OrderId(-1)) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/FinalLatencyBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | 4 | import java.io.File 5 | import highperfscala.orderbook.Commands.Command 6 | import highperfscala.orderbook.OrderBook 7 | import org.mpierce.metrics.reservoir.hdrhistogram.HdrHistogramReservoir 8 | import util._ 9 | 10 | import scala.annotation.tailrec 11 | 12 | object FinalLatencyBenchmark { 13 | 14 | case class CommandsPerSecond(value: Int) extends AnyVal 15 | case class BenchmarkIterationCount(value: Int) extends AnyVal 16 | case class CommandSentTimestamp(value: Long) extends AnyVal 17 | 18 | def runBenchmark( 19 | sampleCommands: List[Command], 20 | cps: CommandsPerSecond, 21 | count: BenchmarkIterationCount): Unit = { 22 | val totalCommandCount = cps.value * count.value 23 | 24 | jvmWarmUp(sampleCommands) 25 | 26 | @tailrec 27 | def sendCommands( 28 | xs: List[(Command, Int)], 29 | ob: OrderBook, 30 | testStart: Long, 31 | histogram: HdrHistogramReservoir): (OrderBook, HdrHistogramReservoir) = 32 | xs match { 33 | case head :: tail => 34 | val (command, offsetInMs) = head 35 | val shouldStart = testStart + offsetInMs 36 | 37 | while (shouldStart > System.currentTimeMillis()) { 38 | // keep the thread busy while waiting for the next batch to be sent 39 | } 40 | 41 | val newBook = OrderBook.handle(ob, command)._1 42 | val operationEnd = System.currentTimeMillis() 43 | histogram.update(operationEnd - shouldStart) 44 | 45 | sendCommands(tail, newBook, testStart, histogram) 46 | case Nil => (ob, histogram) 47 | } 48 | 49 | val (_, histogram) = sendCommands( 50 | generateCount(sampleCommands, totalCommandCount) 51 | .grouped(cps.value) 52 | .toList.zipWithIndex 53 | .flatMap { 54 | case (secondBatch, sBatchIndex) => 55 | val batchOffsetInMs = sBatchIndex * 1000 56 | val commandIntervalInMs = 1000.0 / cps.value 57 | secondBatch.zipWithIndex.map { 58 | case (command, commandIndex) => 59 | val commandOffsetInMs = 60 | Math.floor(commandIntervalInMs * commandIndex).toInt 61 | (command, batchOffsetInMs + commandOffsetInMs) 62 | } 63 | }, 64 | OrderBook.empty, 65 | System.currentTimeMillis(), 66 | new HdrHistogramReservoir()) 67 | 68 | printSnapshot(histogram.getSnapshot) 69 | } 70 | 71 | def main(args: Array[String]): Unit = { 72 | runBenchmark(DataCodec.read(new File(args(0))), 73 | CommandsPerSecond(args(1).toInt), 74 | BenchmarkIterationCount(args(2).toInt)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/FirstLatencyBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.benchmarks 2 | 3 | import java.io.File 4 | 5 | import highperfscala.benchmarks.util._ 6 | import highperfscala.orderbook.Commands.Command 7 | import highperfscala.orderbook.OrderBook 8 | import org.mpierce.metrics.reservoir.hdrhistogram.HdrHistogramReservoir 9 | 10 | import scala.annotation.tailrec 11 | 12 | object FirstLatencyBenchmark { 13 | 14 | def main(args: Array[String]): Unit = { 15 | 16 | val commandSample = DataCodec.read(new File(args(0))) 17 | val (commandsPerSecond, iterations) = (args(1).toInt, args(2).toInt) 18 | val totalCommandCount = commandsPerSecond * iterations 19 | 20 | jvmWarmUp(commandSample) 21 | 22 | @tailrec 23 | def sendCommands( 24 | xs: List[(List[Command], Int)], 25 | ob: OrderBook, 26 | testStart: Long, 27 | histogram: HdrHistogramReservoir): (OrderBook, HdrHistogramReservoir) = 28 | xs match { 29 | case head :: tail => 30 | val (batch, offsetInSeconds) = head 31 | val shouldStart = testStart + (1000 * offsetInSeconds) 32 | 33 | while (shouldStart > System.currentTimeMillis()) { 34 | // keep the thread busy while waiting for the next batch to be sent 35 | } 36 | 37 | val updatedBook = batch.foldLeft(ob) { 38 | case (accBook, c) => 39 | val operationStart = System.currentTimeMillis() 40 | val newBook = OrderBook.handle(accBook, c)._1 41 | val operationEnd = System.currentTimeMillis() 42 | // record latency 43 | histogram.update(operationEnd - operationStart) 44 | newBook 45 | } 46 | 47 | sendCommands(tail, updatedBook, testStart, histogram) 48 | case Nil => (ob, histogram) 49 | } 50 | 51 | val (_, histogram) = sendCommands( 52 | // Organizes commands per 1 second batches 53 | generateCount(commandSample, totalCommandCount) 54 | .grouped(commandsPerSecond).zipWithIndex 55 | .toList, 56 | OrderBook.empty, 57 | System.currentTimeMillis(), 58 | new HdrHistogramReservoir()) 59 | 60 | printSnapshot(histogram.getSnapshot) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/IoExample.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | 4 | import java.io._ 5 | import java.net._ 6 | 7 | object IoExample { 8 | 9 | def main(args: Array[String]): Unit = { 10 | 11 | new Thread(new Runnable { 12 | def run(): Unit = { 13 | var messageCount = 0 14 | val welcomeSocket = new ServerSocket(6789) 15 | val connectionSocket = welcomeSocket.accept() 16 | val inFromClient = new BufferedReader(new InputStreamReader( 17 | connectionSocket.getInputStream())) 18 | while (messageCount < 10000) { 19 | val clientSentence = inFromClient.readLine() 20 | System.out.println("Received: " + clientSentence) 21 | messageCount = messageCount + 1 22 | } 23 | } 24 | }).start() 25 | 26 | Thread.sleep(100) 27 | 28 | val clientSocket = new Socket("localhost", 6789) 29 | val outToServer = new DataOutputStream(clientSocket.getOutputStream()) 30 | val pw = new PrintWriter("/tmp/foobar") 31 | for (i <- 1 to 10000) { 32 | println("Writing message") 33 | outToServer.writeBytes("hello\n") 34 | Thread.sleep(1) 35 | pw.write("hello\n") 36 | } 37 | pw.close() 38 | clientSocket.close() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/PseudoBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.benchmarks 2 | 3 | import highperfscala.orderbook._ 4 | import highperfscala.orderbook.Commands._ 5 | 6 | import scala.util.Random 7 | 8 | object PseudoBenchmark { 9 | def main(args: Array[String]): Unit = { 10 | // """Given empty book 11 | // |and buy limit order added 12 | // |and second buy limit order added 13 | // |and first buy limit order canceled 14 | // |When market sell order arrives 15 | // |Then OrderExecuted 16 | // """.stripMargin 17 | val commands = Array[Command]( 18 | AddLimitOrder(BuyLimitOrder(OrderId(1), Price(BigDecimal(2.00)))), 19 | AddLimitOrder(BuyLimitOrder(OrderId(2), Price(BigDecimal(2.00)))), 20 | CancelOrder(OrderId(1))) 21 | 22 | def nextCommandIndex(i: Int): Int = { 23 | def randomized(): Int = Random.nextInt(commands.length) 24 | def sequential(): Int = i % commands.length 25 | randomized() 26 | } 27 | 28 | 29 | println { 30 | (0 to 100000000).foldLeft(OrderBook.empty) { case (ob, i) => 31 | OrderBook.handle(ob, commands(nextCommandIndex(i)))._1 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/SecondLatencyBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | 4 | import java.io.File 5 | import highperfscala.orderbook.Commands.Command 6 | import highperfscala.orderbook.OrderBook 7 | import org.mpierce.metrics.reservoir.hdrhistogram.HdrHistogramReservoir 8 | import util._ 9 | 10 | import scala.annotation.tailrec 11 | 12 | object SecondLatencyBenchmark { 13 | 14 | def main(args: Array[String]): Unit = { 15 | 16 | val commandSample = DataCodec.read(new File(args(0))) 17 | val (commandsPerSecond, iterations) = (args(1).toInt, args(2).toInt) 18 | val totalCommandCount = commandsPerSecond * iterations 19 | 20 | jvmWarmUp(commandSample) 21 | 22 | @tailrec 23 | def sendCommands( 24 | xs: List[(List[Command], Int)], 25 | ob: OrderBook, 26 | testStart: Long, 27 | histogram: HdrHistogramReservoir): (OrderBook, HdrHistogramReservoir) = 28 | xs match { 29 | case head :: tail => 30 | val (batch, offsetInSeconds) = head 31 | val shouldStart = testStart + (1000 * offsetInSeconds) 32 | 33 | while (shouldStart > System.currentTimeMillis()) { 34 | // keep the thread busy while waiting for the next batch to be sent 35 | } 36 | 37 | val updatedBook = batch.foldLeft(ob) { 38 | case (accBook, c) => 39 | val newBook = OrderBook.handle(accBook, c)._1 40 | val operationEnd = System.currentTimeMillis() 41 | // record latency 42 | histogram.update(operationEnd - shouldStart) 43 | newBook 44 | } 45 | 46 | sendCommands(tail, updatedBook, testStart, histogram) 47 | case Nil => (ob, histogram) 48 | } 49 | 50 | val (_, histogram) = sendCommands( 51 | // Organizes commands per 1 second batches 52 | generateCount(commandSample, totalCommandCount) 53 | .grouped(commandsPerSecond).zipWithIndex 54 | .toList, 55 | OrderBook.empty, 56 | System.currentTimeMillis(), 57 | new HdrHistogramReservoir()) 58 | 59 | printSnapshot(histogram.getSnapshot) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/ThroughputBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | 4 | import java.io.File 5 | import highperfscala.orderbook.OrderBook 6 | import util._ 7 | 8 | object ThroughputBenchmark { 9 | 10 | def main(args: Array[String]): Unit = { 11 | 12 | val commandSample = DataCodec.read(new File(args(0))) 13 | val commandCount = args(1).toInt 14 | 15 | jvmWarmUp(commandSample) 16 | 17 | val commands = generateCount(commandSample, commandCount) 18 | 19 | val start = System.currentTimeMillis() 20 | commands.foldLeft(OrderBook.empty)(OrderBook.handle(_, _)._1) 21 | val end = System.currentTimeMillis() 22 | val delayInSeconds = (end - start) / 1000.0 23 | 24 | println { 25 | s""" 26 | |Processed ${commands.size} commands 27 | |in $delayInSeconds seconds 28 | |Throughput: ${commands.size / delayInSeconds} operations/sec""" 29 | .stripMargin 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/util/DataCodec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | package util 4 | 5 | import java.io._ 6 | 7 | import orderbook.Commands.Command 8 | import org.slf4s.Logging 9 | 10 | import scala.collection.mutable.ListBuffer 11 | 12 | /** 13 | * This module provides functions to persist to/load from disk a list of 14 | * commands. 15 | */ 16 | object DataCodec extends Logging { 17 | 18 | def write(cs: List[Command], output: File): Unit = { 19 | val oos = new ObjectOutputStream(new FileOutputStream(output)) 20 | cs.foreach(oos.writeObject) 21 | oos.close() 22 | } 23 | 24 | def read(input: File): List[Command] = { 25 | val fis = new FileInputStream(input) 26 | val ois = new ObjectInputStream(fis) 27 | val commandBuilder = ListBuffer[Command]() 28 | while(fis.available() != 0) { 29 | commandBuilder.append(ois.readObject().asInstanceOf[Command]) 30 | } 31 | ois.close() 32 | fis.close() 33 | 34 | commandBuilder.result() 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/util/DataGenerator.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | package util 4 | 5 | object DataGenerator { 6 | 7 | import java.io.File 8 | 9 | import orderbook.Commands.{AddLimitOrder, CancelOrder, Command} 10 | import orderbook._ 11 | import org.scalacheck.Gen 12 | 13 | import scala.util.Random 14 | 15 | /** 16 | * Generates fake historical data consisting of a list of commands to 17 | * be applied to an order book. 18 | */ 19 | def main(args: Array[String]): Unit = { 20 | 21 | val outputFile = new File(args(0)) 22 | val commandCount = args(1).toInt 23 | 24 | val orderedCommands = Random.shuffle( 25 | Gen.listOfN(commandCount / 2, LimitOrder.genLimitOrder).sample.get 26 | .map { order => 27 | val nextCommand = 28 | if (cancelOrder()) CancelOrder(order.id) 29 | else AddLimitOrder(oppositeOrder(order)) 30 | 31 | List(AddLimitOrder(order), nextCommand) 32 | }) 33 | 34 | val commands = merge(orderedCommands, Nil) 35 | 36 | DataCodec.write(commands, outputFile) 37 | } 38 | 39 | /** 40 | * Randomly decide if an order should be canceled 41 | */ 42 | private def cancelOrder(): Boolean = Random.nextInt(3) == 0 43 | 44 | private def oppositeOrder(o: LimitOrder): LimitOrder = o match { 45 | case BuyLimitOrder(_, price) => 46 | SellLimitOrder(OrderId.genOrderId.sample.get, price) 47 | case SellLimitOrder(_, price) => 48 | BuyLimitOrder(OrderId.genOrderId.sample.get, price) 49 | } 50 | 51 | /** 52 | * Merge a list of pairs, introducing randomness while making sure that 53 | * an order's complement comes later in the stream. 54 | */ 55 | private def merge(ls: List[List[Command]], acc: List[Command]): List[Command] = 56 | ls match { 57 | case Nil => acc.reverse 58 | case List(c) :: rest => merge(rest, c :: acc) 59 | case List(c1, c2) :: rest => 60 | merge(Random.shuffle(List(c2) :: rest), c1 :: acc) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/benchmarks/util/package.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package benchmarks 3 | 4 | import com.codahale.metrics.Snapshot 5 | import orderbook.Commands.Command 6 | import orderbook.OrderBook 7 | import org.slf4s.Logging 8 | 9 | package object util extends Logging { 10 | 11 | def infiniteCommands(sample: List[Command]): Stream[Command] = 12 | Stream.continually(sample.toStream).flatten 13 | 14 | def generateCount(sample: List[Command], count: Int): List[Command] = 15 | infiniteCommands(sample).take(count).toList 16 | 17 | def jvmWarmUp(sample: List[Command]): Unit = { 18 | log.debug("Begin warm up") 19 | val commands = util.generateCount(sample, 100000) 20 | commands.foldLeft(OrderBook.empty) { 21 | case (book, command) => OrderBook.handle(book, command)._1 22 | } 23 | log.debug("End warm up") 24 | } 25 | 26 | def printSnapshot(s: Snapshot): Unit = println { 27 | s""" 28 | |Processed ${s.size} commands 29 | |99p latency: ${s.get99thPercentile()} ms 30 | |99.9p latency: ${s.get999thPercentile()} ms 31 | |Maximum latency: ${s.getMax} ms 32 | """.stripMargin 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/orderbook/OrderBook.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package orderbook 3 | 4 | import orderbook.Commands._ 5 | import orderbook.Events._ 6 | 7 | import scala.collection.immutable.{Queue, TreeMap} 8 | 9 | // http://web.archive.org/web/20110312023826/http://www.quantcup.org/home/howtohft_howtobuildafastlimitorderbook 10 | case class OrderBook( 11 | bids: TreeMap[Price, Queue[BuyLimitOrder]], 12 | offers: TreeMap[Price, Queue[SellLimitOrder]]) { 13 | def bestBid: Option[BuyLimitOrder] = bids.lastOption.flatMap(_._2.headOption) 14 | def bestOffer: Option[SellLimitOrder] = 15 | offers.headOption.flatMap(_._2.headOption) 16 | } 17 | 18 | object OrderBook { 19 | val noEvent: Option[Event] = None 20 | val empty: OrderBook = OrderBook( 21 | TreeMap.empty[Price, Queue[BuyLimitOrder]], 22 | TreeMap.empty[Price, Queue[SellLimitOrder]]) 23 | 24 | // Could make sense to handle with State monad 25 | def handle(ob: OrderBook, c: Command): (OrderBook, Event) = c match { 26 | case AddLimitOrder(o) => handleAddLimitOrder(ob, o) 27 | case CancelOrder(id) => handleCancelOrder(ob, id) 28 | } 29 | 30 | private def handleAddLimitOrder( 31 | ob: OrderBook, o: LimitOrder): (OrderBook, Event) = o match { 32 | case oo: BuyLimitOrder => 33 | ob.bestOffer.exists(oo.price.value >= _.price.value) match { 34 | case true => crossBookBuy(ob, oo) 35 | case false => 36 | val orders = ob.bids.getOrElse(oo.price, Queue.empty) 37 | ob.copy(bids = ob.bids + (oo.price -> orders.enqueue(oo))) -> 38 | LimitOrderAdded 39 | } 40 | case oo: SellLimitOrder => 41 | ob.bestBid.exists(oo.price.value <= _.price.value) match { 42 | case true => crossBookSell(ob, oo) 43 | case false => 44 | val orders = ob.offers.getOrElse(oo.price, Queue.empty) 45 | ob.copy(offers = ob.offers + (oo.price -> orders.enqueue(oo))) -> 46 | LimitOrderAdded 47 | } 48 | } 49 | 50 | private def handleCancelOrder( 51 | ob: OrderBook, id: OrderId): (OrderBook, Event) = { 52 | ob.bids.find { case (p, q) => q.exists(_.id == id) }.fold( 53 | ob.offers.find { case (p, q) => q.exists(_.id == id) }.fold( 54 | ob -> Event.orderCancelRejected) { case (p, q) => 55 | // It's awkward to duplicate the queue remove logic, but the different 56 | // types for bid vs offer make it difficult to share the code 57 | val updatedQ = q.filter(_.id != id) 58 | ob.copy(offers = updatedQ.nonEmpty match { 59 | case true => ob.offers + (p -> updatedQ) 60 | case false => ob.offers - p 61 | }) -> OrderCanceled 62 | }) { case (p, q) => 63 | val updatedQ = q.filter(_.id != id) 64 | ob.copy(bids = updatedQ.nonEmpty match { 65 | case true => ob.bids + (p -> updatedQ) 66 | case false => ob.bids - p 67 | }) -> OrderCanceled 68 | } 69 | } 70 | 71 | private def crossBookSell( 72 | ob: OrderBook, s: SellLimitOrder): (OrderBook, Event) = 73 | ob.bids.lastOption.fold(handleAddLimitOrder(ob, s)) { case (_, xs) => 74 | val (o, qq) = xs.dequeue 75 | (ob.copy(bids = qq.isEmpty match { 76 | case true => ob.bids - o.price 77 | case false => ob.bids + (o.price -> qq) 78 | }), OrderExecuted(Execution(o.id, o.price), Execution(s.id, o.price))) 79 | } 80 | 81 | private def crossBookBuy( 82 | ob: OrderBook, b: BuyLimitOrder): (OrderBook, Event) = 83 | ob.offers.headOption.fold(handleAddLimitOrder(ob, b)) { case (_, xs) => 84 | val (o, qq) = xs.dequeue 85 | (ob.copy(offers = qq.isEmpty match { 86 | case true => ob.offers - o.price 87 | case false => ob.offers + (o.price -> qq) 88 | }), OrderExecuted(Execution(b.id, o.price), Execution(o.id, o.price))) 89 | } 90 | } 91 | 92 | object Commands { 93 | sealed trait Command 94 | case class AddLimitOrder(o: LimitOrder) extends Command 95 | case class CancelOrder(id: OrderId) extends Command 96 | } 97 | 98 | object Events { 99 | sealed trait Event 100 | object Event { 101 | val orderCancelRejected: Event = OrderCancelRejected 102 | } 103 | case class OrderExecuted(buy: Execution, sell: Execution) extends Event 104 | case object LimitOrderAdded extends Event 105 | case object OrderCancelRejected extends Event 106 | case object OrderCanceled extends Event 107 | } 108 | -------------------------------------------------------------------------------- /chapter2/src/main/scala/highperfscala/orderbook/model.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package orderbook 3 | 4 | import org.scalacheck.Gen 5 | 6 | case class Price(value: BigDecimal) 7 | object Price { 8 | implicit val genPrice: Gen[Price] = Gen.posNum[Double].map(d => 9 | Price(BigDecimal(d))) 10 | implicit val ordering: Ordering[Price] = new Ordering[Price] { 11 | def compare(x: Price, y: Price): Int = 12 | Ordering.BigDecimal.compare(x.value, y.value) 13 | } 14 | } 15 | 16 | case class OrderId(value: Long) 17 | object OrderId { 18 | implicit val genOrderId: Gen[OrderId] = Gen.posNum[Long].map(OrderId.apply) 19 | } 20 | 21 | sealed trait LimitOrder { 22 | def id: OrderId 23 | def price: Price 24 | } 25 | object LimitOrder { 26 | implicit val genLimitOrder: Gen[LimitOrder] = Gen.oneOf( 27 | BuyLimitOrder.genBuyLimitOrder, SellLimitOrder.genSellLimitOrder) 28 | } 29 | case class BuyLimitOrder(id: OrderId, price: Price) extends LimitOrder 30 | object BuyLimitOrder { 31 | implicit val genBuyLimitOrder: Gen[BuyLimitOrder] = Gen.zip( 32 | OrderId.genOrderId, Price.genPrice).map(Function.tupled(BuyLimitOrder.apply)) 33 | } 34 | case class SellLimitOrder(id: OrderId, price: Price) extends LimitOrder 35 | object SellLimitOrder { 36 | implicit val genSellLimitOrder: Gen[SellLimitOrder] = Gen.zip( 37 | OrderId.genOrderId, Price.genPrice).map(Function.tupled( 38 | SellLimitOrder.apply)) 39 | } 40 | 41 | case class Execution(orderId: OrderId, price: Price) -------------------------------------------------------------------------------- /chapter2/src/test/scala/highperfscala/orderbook/OrderBookCancelingSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package orderbook 3 | 4 | import highperfscala.orderbook.Commands.{AddLimitOrder, CancelOrder} 5 | import highperfscala.orderbook.Events.{OrderCanceled, OrderCancelRejected} 6 | import org.scalacheck.Prop 7 | import org.specs2.ScalaCheck 8 | import org.specs2.mutable.Specification 9 | 10 | class OrderBookCancelingSpec extends Specification with ScalaCheck { 11 | 12 | """Given empty book 13 | |When cancel order arrives 14 | |Then OrderCancelRejected 15 | """.stripMargin ! Prop.forAll(OrderId.genOrderId) { id => 16 | OrderBook.handle(OrderBook.empty, CancelOrder(id))._2 ==== 17 | OrderCancelRejected 18 | } 19 | 20 | """Given empty book 21 | |and buy limit order added 22 | |When cancel order arrives 23 | |Then OrderCanceled 24 | """.stripMargin ! Prop.forAll(BuyLimitOrder.genBuyLimitOrder) { o => 25 | (OrderBook.handle(_: OrderBook, AddLimitOrder(o))).andThen { 26 | case (ob, _) => OrderBook.handle(ob, CancelOrder(o.id))._2 27 | }(OrderBook.empty) ==== OrderCanceled 28 | } 29 | 30 | """Given empty book 31 | |and sell limit order added 32 | |When cancel order arrives 33 | |Then OrderCanceled 34 | """.stripMargin ! Prop.forAll(SellLimitOrder.genSellLimitOrder) { o => 35 | (OrderBook.handle(_: OrderBook, AddLimitOrder(o))).andThen { 36 | case (ob, _) => OrderBook.handle(ob, CancelOrder(o.id))._2 37 | }(OrderBook.empty) ==== OrderCanceled 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /chapter2/src/test/scala/highperfscala/orderbook/OrderBookTradingSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import highperfscala.orderbook.Commands.{CancelOrder, AddLimitOrder} 4 | import highperfscala.orderbook.Events.{OrderCancelRejected, OrderExecuted, LimitOrderAdded} 5 | import org.scalacheck.Prop 6 | import org.specs2.ScalaCheck 7 | import org.specs2.mutable.Specification 8 | 9 | class OrderBookTradingSpec extends Specification with ScalaCheck { 10 | 11 | """Given empty book 12 | |When limit order arrives 13 | |Then LimitOrderAdded 14 | """.stripMargin ! Prop.forAll(LimitOrder.genLimitOrder) { l => 15 | OrderBook.handle(OrderBook.empty, AddLimitOrder(l))._2 ==== LimitOrderAdded 16 | } 17 | 18 | """Given empty book 19 | |and buy limit order added 20 | |When resting sell limit order arrives 21 | |Then LimitOrderAdded 22 | """.stripMargin ! Prop.forAll(BuyLimitOrder.genBuyLimitOrder, 23 | OrderId.genOrderId) { (buy, id) => 24 | (OrderBook.handle(_: OrderBook, AddLimitOrder(buy))).andThen { 25 | case (ob, _) => OrderBook.handle(ob, AddLimitOrder( 26 | SellLimitOrder(id, Price(buy.price.value + 0.01))))._2 27 | }(OrderBook.empty) ==== LimitOrderAdded 28 | } 29 | 30 | """Given empty book 31 | |and sell limit order added 32 | |When resting buy limit order arrives 33 | |Then LimitOrderAdded 34 | """.stripMargin ! Prop.forAll(SellLimitOrder.genSellLimitOrder, 35 | OrderId.genOrderId) { (sell, id) => 36 | (OrderBook.handle(_: OrderBook, AddLimitOrder(sell))).andThen { 37 | case (ob, _) => OrderBook.handle(ob, AddLimitOrder( 38 | BuyLimitOrder(id, Price(sell.price.value - 0.01))))._2 39 | }(OrderBook.empty) ==== LimitOrderAdded 40 | } 41 | 42 | """Given empty book 43 | |and buy limit order added 44 | |When crossing sell limit order arrives 45 | |Then OrderExecuted 46 | """.stripMargin ! Prop.forAll(BuyLimitOrder.genBuyLimitOrder, 47 | OrderId.genOrderId) { (buy, id) => 48 | (OrderBook.handle(_: OrderBook, AddLimitOrder(buy))).andThen { 49 | case (ob, _) => OrderBook.handle(ob, AddLimitOrder( 50 | SellLimitOrder(id, Price(buy.price.value - 0.01))))._2 51 | }(OrderBook.empty) ==== OrderExecuted( 52 | Execution(buy.id, buy.price), Execution(id, buy.price)) 53 | } 54 | 55 | """Given empty book 56 | |and sell limit order added 57 | |When crossing buy limit order arrives 58 | |Then OrderExecuted 59 | """.stripMargin ! Prop.forAll(SellLimitOrder.genSellLimitOrder, 60 | OrderId.genOrderId) { (sell, id) => 61 | (OrderBook.handle(_: OrderBook, AddLimitOrder(sell))).andThen { 62 | case (ob, _) => OrderBook.handle(ob, AddLimitOrder( 63 | BuyLimitOrder(id, Price(sell.price.value + 0.01))))._2 64 | }(OrderBook.empty) ==== OrderExecuted( 65 | Execution(id, sell.price), Execution(sell.id, sell.price)) 66 | } 67 | 68 | """Given empty book 69 | |When cancel order command arrives 70 | |Then OrderCancelRejected 71 | """.stripMargin ! Prop.forAll(OrderId.genOrderId) { id => 72 | OrderBook.handle(OrderBook.empty, CancelOrder(id))._2 ==== 73 | OrderCancelRejected 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /chapter2/target/.history: -------------------------------------------------------------------------------- 1 | set javaOptions := Seq("-Xmx1G", "-XX:+UnlockCommercialFeatures", "-XX:+FlightRecorder", "-XX:FlightRecorderOptions=defaultrecording=true,dumponexit=true,dumponexitpath=/tmp/order-book.jfr") 2 | runMain highperfscala.benchmarks.ThroughputBenchmark resources/historical_data 2000000 3 | runMain highperfscala.benchmarks.ThroughputBenchmark src/main/resources/historical_data 2000000 4 | pwd 5 | runMain highperfscala.benchmarks.ThroughputBenchmark chapter2/src/main/resources/historical_data 2000000 6 | compile 7 | runMain highperfscala.benchmarks.IoExample 8 | set javaOptions := Seq("-Xmx1G", "-XX:+UnlockCommercialFeatures", "-XX:+FlightRecorder", "-XX:FlightRecorderOptions=defaultrecording=true,dumponexit=true,dumponexitpath=/tmp/order-book.jfr") 9 | run chapter2/src/main/resources/historical_data 2000000 10 | run chapter2/src/main/resources/historical_data 100000 11 | run chapter2/src/main/resources/historical_data 25000 12 | run ~/historical_data 25000 13 | run historical_data 25000 14 | run historical_data 100000 15 | run historical_data 25000 16 | -------------------------------------------------------------------------------- /chapter2/target/streams/$global/clean/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter2/target/streams/$global/clean/$global/streams/out -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/anyval/TaggedTypes.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.anyval 2 | 3 | import scala.util.Random 4 | import scalaz.{@@, Tag} 5 | 6 | object TaggedTypes { 7 | 8 | sealed trait PriceTag 9 | 10 | type Price = BigDecimal @@ PriceTag 11 | 12 | object Price { 13 | def newPrice(p: BigDecimal): Price = 14 | Tag[BigDecimal, PriceTag](p) 15 | 16 | def lowerThan(a: Price, b: Price): Boolean = 17 | Tag.unwrap(a) < Tag.unwrap(b) 18 | } 19 | 20 | def newPriceArray(count: Int): Array[Price] = { 21 | val a = new Array[Price](count) 22 | for (i <- 0 until count) { 23 | a(i) = Price.newPrice(BigDecimal(Random.nextInt())) 24 | } 25 | a 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/anyval/ValueClasses.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.anyval 2 | 3 | import scala.util.Random 4 | 5 | object ValueClasses { 6 | 7 | case class Price(value: BigDecimal) extends AnyVal { 8 | 9 | def lowerThan(p: Price): Boolean = this.value < p.value 10 | 11 | } 12 | 13 | case class OrderId(value: Long) extends AnyVal 14 | 15 | def printInfo(p: Price, oId: OrderId): Unit ={ 16 | println(s"Price: ${p.value}, ID: ${oId.value}") 17 | } 18 | 19 | def newPriceArray(count: Int): Array[Price] = { 20 | val a = new Array[Price](count) 21 | for(i <- 0 until count){ 22 | a(i) = Price(BigDecimal(Random.nextInt())) 23 | } 24 | a 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/NullOptionApp.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | object NullOptionApp extends Program[NullOption] 4 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/OptionBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations.Mode._ 6 | import org.openjdk.jmh.annotations.{BenchmarkMode, _} 7 | 8 | import scalaz.@@ 9 | import OptionBenchmarks._ 10 | 11 | @BenchmarkMode(Array(Throughput)) 12 | @OutputTimeUnit(TimeUnit.SECONDS) 13 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 14 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 15 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 16 | class OptionBenchmarks { 17 | 18 | @Benchmark 19 | def scalaOption(s: OptionState): Option[ShareCount] = { 20 | val c = s.counter 21 | s.counter = s.counter + 1 22 | c % s.someFrequency match { 23 | case 0 => Some(ShareCount(s.counter)) 24 | case _ => None 25 | } 26 | } 27 | 28 | @Benchmark 29 | def nullOption(s: OptionState): ShareCount @@ Opt = { 30 | OptOps.some(ShareCount(s.counter)) 31 | val c = s.counter 32 | s.counter = s.counter + 1 33 | c % s.someFrequency match { 34 | case 0 => OptOps.some(ShareCount(s.counter)) 35 | case _ => OptOps.none 36 | } 37 | } 38 | 39 | @Benchmark 40 | def nullOptionNoneReused(s: OptionState): ShareCount @@ Opt = { 41 | val c = s.counter 42 | s.counter = s.counter + 1 43 | c % s.someFrequency match { 44 | case 0 => OptOps.some(ShareCount(s.counter)) 45 | case _ => OptionBenchmarks.noShareCount 46 | } 47 | } 48 | } 49 | 50 | object OptionBenchmarks { 51 | 52 | val noShareCount = OptOps.none[ShareCount] 53 | 54 | case class ShareCount(value: Long) extends AnyVal 55 | 56 | @State(Scope.Benchmark) 57 | class OptionState { 58 | 59 | @Param(Array("1", "2", "3", "5")) 60 | var someFrequency: Int = 0 61 | 62 | var counter: Long = 0 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/OptionCreationBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations.Mode._ 6 | import org.openjdk.jmh.annotations.{BenchmarkMode, _} 7 | import OptionCreationBenchmarks._ 8 | 9 | import scalaz.@@ 10 | 11 | @BenchmarkMode(Array(Throughput)) 12 | @OutputTimeUnit(TimeUnit.SECONDS) 13 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 14 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 15 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 16 | class OptionCreationBenchmarks { 17 | 18 | @Benchmark 19 | def scalaSome(): Option[ShareCount] = Some(ShareCount(1)) 20 | 21 | @Benchmark 22 | def scalaNone(): Option[ShareCount] = None 23 | 24 | @Benchmark 25 | def optSome(): ShareCount @@ Opt = OptOps.some(ShareCount(1)) 26 | 27 | @Benchmark 28 | def optSomeWithNullChecking(): ShareCount @@ Opt = 29 | OptOps.nullCheckingSome(ShareCount(1)) 30 | 31 | @Benchmark 32 | def optNone(): ShareCount @@ Opt = OptOps.none 33 | 34 | @Benchmark 35 | def optNoneReuse(): ShareCount @@ Opt = noShares 36 | } 37 | 38 | object OptionCreationBenchmarks { 39 | case class ShareCount(value: Long) extends AnyVal 40 | val noShares: ShareCount @@ Opt = OptOps.none 41 | } 42 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/OptionFoldingBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import highperfscala.option.OptionBenchmarks.ShareCount 6 | import org.openjdk.jmh.annotations.{Fork, _} 7 | import org.openjdk.jmh.annotations.Mode._ 8 | import OptionFoldingBenchmarks._ 9 | 10 | import scalaz.@@ 11 | 12 | @BenchmarkMode(Array(Throughput)) 13 | @OutputTimeUnit(TimeUnit.SECONDS) 14 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 15 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 16 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 17 | class OptionFoldingBenchmarks { 18 | 19 | @Benchmark 20 | def scalaOption(): ShareCount = 21 | scalaSome.fold(ShareCount(0))(c => ShareCount(c.value * 2)) 22 | 23 | 24 | @Benchmark 25 | def optOption(): ShareCount = 26 | OptOps.fold(optSome)(ShareCount(0))(c => ShareCount(c.value * 2)) 27 | 28 | } 29 | 30 | object OptionFoldingBenchmarks { 31 | 32 | case class ShareCount(value: Long) extends AnyVal 33 | 34 | val scalaSome: Option[ShareCount] = Some(ShareCount(7)) 35 | val optSome: ShareCount @@ Opt = OptOps.some(ShareCount(7)) 36 | } 37 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/OptionzTest.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | object OptionzTest { 4 | def main(args: Array[String]): Unit = { 5 | 6 | val some = OptOps.fold(OptOps.some(25))(7)(_ * 2) 7 | println(some) 8 | assert(some == 50) 9 | 10 | val none = OptOps.fold(OptOps.none[Int])(7)(_ * 2) 11 | println(none) 12 | assert(none == 7) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/Program.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | 4 | class Program[Sig <: OptionSig : OptionOps] extends App { 5 | 6 | val ops = OptionOps[Sig] 7 | import ops._ 8 | 9 | // a little dance to derive our Show instance 10 | import scalaz.std.anyVal.intInstance 11 | val showOptOptInt = { 12 | implicit val showOptInt = OptionShow[Sig].optionShow[Int] 13 | OptionShow[Sig].optionShow[Sig#Option[Int]] 14 | } 15 | 16 | // scalaz's syntax tricks are awesome 17 | import showOptOptInt.showSyntax._ 18 | 19 | val optOpt = some(some(42)) 20 | 21 | println("optOpt: " + optOpt.shows) 22 | 23 | val optNone = some(none) 24 | 25 | println("optNone: " + optNone.shows) 26 | 27 | } -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/ScalaOptionExample.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | object ScalaOptionExample { 4 | 5 | def optionalInt(i: Int): Option[Int] = Some(i) 6 | 7 | } 8 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/abstractAdt.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | import scala.language.higherKinds 4 | 5 | // Taken from https://bertails.org/2015/02/15/abstract-algebraic-data-type/ 6 | trait OptionSig { 7 | type Option[+_] 8 | type Some[+A] <: Option[A] 9 | type None <: Option[Nothing] 10 | } 11 | 12 | abstract class OptionOps[Sig <: OptionSig] { 13 | def some[A](x: A): Sig#Some[A] 14 | def none: Sig#None 15 | def fold[A, B](opt: Sig#Option[A])(ifNone: => B, ifSome: A => B): B 16 | } 17 | 18 | object OptionOps { 19 | def apply[Sig <: OptionSig](implicit ops: OptionOps[Sig]): OptionOps[Sig] = 20 | ops 21 | } 22 | 23 | import scalaz.Show 24 | 25 | class OptionShow[Sig <: OptionSig : OptionOps] { 26 | 27 | def optionShow[A: Show]: Show[Sig#Option[A]] = { 28 | 29 | // retrieving the typeclass instances 30 | val showA = Show[A] 31 | val ops = OptionOps[Sig] 32 | 33 | val instance = new Show[Sig#Option[A]] { 34 | override def shows(opt: Sig#Option[A]): String = ops.fold(opt)( 35 | "none", 36 | x => s"some(${showA.shows(x)})" 37 | ) 38 | } 39 | 40 | instance 41 | } 42 | 43 | } 44 | 45 | object OptionShow { 46 | 47 | implicit def apply[Sig <: OptionSig : OptionOps]: OptionShow[Sig] = 48 | new OptionShow[Sig] 49 | } 50 | 51 | trait ScalaOption extends OptionSig { 52 | 53 | type Option[+A] = scala.Option[A] 54 | type Some[+A] = scala.Some[A] 55 | type None = scala.None.type 56 | 57 | } 58 | 59 | object ScalaOption { 60 | 61 | implicit object ops extends OptionOps[ScalaOption] { 62 | 63 | def some[A](x: A): ScalaOption#Some[A] = scala.Some(x) 64 | 65 | val none: ScalaOption#None = scala.None 66 | 67 | def fold[A, B](opt: ScalaOption#Option[A])(ifNone: => B, ifSome: A => B): B = 68 | opt match { 69 | case scala.None => ifNone 70 | case scala.Some(x) => ifSome(x) 71 | } 72 | 73 | } 74 | 75 | } 76 | 77 | trait NullOption extends OptionSig { 78 | 79 | type Option[+A] = Any 80 | type Some[+A] = Any 81 | type None = Null 82 | 83 | } 84 | 85 | object NullOption { 86 | 87 | implicit object ops extends OptionOps[NullOption] { 88 | 89 | def some[A](x: A): NullOption#Some[A] = x 90 | 91 | val none: NullOption#None = null 92 | 93 | def fold[A, B](opt: NullOption#Option[A])(ifNone: => B, ifSome: A => B): B = { 94 | if (opt == null) ifNone 95 | else ifSome(opt.asInstanceOf[A]) 96 | } 97 | 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/option/opt.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.option 2 | 3 | import scalaz.{@@, Tag} 4 | 5 | sealed trait Opt 6 | 7 | // Allocation free Option implementation inspired by the 8 | // abstract ADTs using tagged types 9 | object OptOps { 10 | 11 | def some[@specialized A](x: A): A @@ Opt = Tag(x) 12 | def nullCheckingSome[@specialized A](x: A): A @@ Opt = 13 | if (x == null) sys.error("Null values disallowed") else Tag(x) 14 | def none[A]: A @@ Opt = Tag(null.asInstanceOf[A]) 15 | 16 | def isDefined[A](o: A @@ Opt): Boolean = o != null 17 | def isEmpty[A](o: A @@ Opt): Boolean = !isDefined(o) 18 | def unsafeGet[A](o: A @@ Opt): A = 19 | if (isDefined(o)) o.asInstanceOf[A] else sys.error("Cannot get None") 20 | 21 | def fold[A, B](o: A @@ Opt)(ifEmpty: => B)(f: A => B): B = 22 | if (isEmpty(o)) ifEmpty else f(o.asInstanceOf[A]) 23 | } 24 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/patternmatch/PatternMatching.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.patternmatch 2 | 3 | import scala.annotation.switch 4 | import scalaz.{@@, Tag} 5 | 6 | object PatternMatching { 7 | 8 | sealed trait Event 9 | case object E1 extends Event 10 | case object E2 extends Event 11 | case object E3 extends Event 12 | 13 | sealed trait Foo 14 | 15 | case class IntBar(i: Int, b: Bar) 16 | def intBar: IntBar = IntBar(1, Bar(2)) 17 | 18 | def tuple2Boxed(): (Int, Bar) = (1, Bar(2)) 19 | 20 | def tuple2: (Int, Double) = (1, 2.0) 21 | 22 | def tuple3(): (Int, Double, Int) = (1, 2.0, 3) 23 | 24 | case class Triple(x: Int, y: Double, z: Int) 25 | 26 | def triple(): Triple = Triple(1, 2.0, 3) 27 | 28 | 29 | def arrayExample(): Array[Bar] = { 30 | Array(Bar(1), Bar(2), Bar(3)) 31 | // .map(b => b.copy(b.value + 1)) 32 | } 33 | 34 | def arrayIntExample(): Array[Int] = { 35 | Array(1, 2, 3).map(i => i * 2) 36 | // .map(b => b.copy(b.value + 1)) 37 | } 38 | 39 | def tagFoo(i: Int): Int @@ Foo = { 40 | Tag.apply(i) 41 | } 42 | 43 | def useFoo(i: Int @@ Foo): Option[String] = { 44 | Some((5 + Tag.unwrap(i)).toString) 45 | } 46 | 47 | case class Bar(value: Int) extends AnyVal 48 | Bar.unapply(Bar(1)) 49 | 50 | sealed trait Side 51 | case object Buy extends Side 52 | case object Sell extends Side 53 | def handleOrder(s: Side): Boolean = s match { 54 | case Buy => true 55 | case Sell => false 56 | } 57 | 58 | sealed trait Order 59 | case class BuyOrder(price: Double) extends Order 60 | case class SellOrder(price: Double) extends Order 61 | def handleOrder(o: Order): Boolean = o match { 62 | case BuyOrder(price) if price > 2.0 => true 63 | case BuyOrder(_) => false 64 | case SellOrder(_) => false 65 | } 66 | 67 | def handleGuard(e: Bar): Option[String] = e match { 68 | case ee if ee.value > 5 => None 69 | case Bar(4) => Some("4") 70 | } 71 | 72 | val z = 4 73 | def handleInt(e: Int): Option[String] = e match { 74 | case 2 => Some("res") 75 | case 3 => None 76 | case `z` => None 77 | } 78 | 79 | case class ShareCount(value: Int) extends AnyVal 80 | def handleAnyVal(e: ShareCount): Option[String] = e match { 81 | case ShareCount(2) => Some("res") 82 | case ShareCount(3) => None 83 | case ShareCount(4) => None 84 | } 85 | 86 | def processShareCount(sc: ShareCount): Boolean = (sc: @switch) match { 87 | case ShareCount(1) => true 88 | case _ => false 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/patternmatch/PatternMatchingBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.patternmatch 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import highperfscala.patternmatch.PatternMatchingBenchmarks._ 6 | import org.openjdk.jmh.annotations.Mode._ 7 | import org.openjdk.jmh.annotations._ 8 | 9 | @BenchmarkMode(Array(Throughput)) 10 | @OutputTimeUnit(TimeUnit.SECONDS) 11 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 12 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 13 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 14 | class PatternMatchingBenchmarks { 15 | 16 | @Benchmark 17 | def matchIntLiterals(i: PatternMatchState): Int = i.matchIndex match { 18 | case 1 => 1 19 | case 2 => 2 20 | case 3 => 3 21 | case 4 => 4 22 | case 5 => 5 23 | case 6 => 6 24 | case 7 => 7 25 | case 8 => 8 26 | case 9 => 9 27 | case 10 => 10 28 | } 29 | 30 | @Benchmark 31 | def matchIntVariables(ii: PatternMatchState): Int = ii.matchIndex match { 32 | case `a` => 1 33 | case `b` => 2 34 | case `c` => 3 35 | case `d` => 4 36 | case `e` => 5 37 | case `f` => 6 38 | case `g` => 7 39 | case `h` => 8 40 | case `i` => 9 41 | case `j` => 10 42 | } 43 | 44 | @Benchmark 45 | def matchAnyVal(i: PatternMatchState): Int = CheapFoo(i.matchIndex) match { 46 | case CheapFoo(1) => 1 47 | case CheapFoo(2) => 2 48 | case CheapFoo(3) => 3 49 | case CheapFoo(4) => 4 50 | case CheapFoo(5) => 5 51 | case CheapFoo(6) => 6 52 | case CheapFoo(7) => 7 53 | case CheapFoo(8) => 8 54 | case CheapFoo(9) => 9 55 | case CheapFoo(10) => 10 56 | } 57 | 58 | @Benchmark 59 | def matchCaseClass(i: PatternMatchState): Int = 60 | ExpensiveFoo(i.matchIndex) match { 61 | case ExpensiveFoo(1) => 1 62 | case ExpensiveFoo(2) => 2 63 | case ExpensiveFoo(3) => 3 64 | case ExpensiveFoo(4) => 4 65 | case ExpensiveFoo(5) => 5 66 | case ExpensiveFoo(6) => 6 67 | case ExpensiveFoo(7) => 7 68 | case ExpensiveFoo(8) => 8 69 | case ExpensiveFoo(9) => 9 70 | case ExpensiveFoo(10) => 10 71 | } 72 | 73 | } 74 | 75 | object PatternMatchingBenchmarks { 76 | 77 | case class CheapFoo(value: Int) extends AnyVal 78 | 79 | case class ExpensiveFoo(value: Int) 80 | 81 | private val (a, b, c, d, e, f, g, h, i, j) = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 82 | 83 | @State(Scope.Benchmark) 84 | class PatternMatchState { 85 | 86 | @Param(Array("1", "5", "10")) 87 | var matchIndex: Int = 0 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/specialization/Inheritance.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.specialization 2 | 3 | object Inheritance { 4 | 5 | class ParentFoo[@specialized T](t: T) 6 | class ChildFoo[T](t: T) extends ParentFoo[T](t) 7 | 8 | def newChildFoo(i: Int): ChildFoo[Int] = new ChildFoo(i) 9 | 10 | trait ParentBar[@specialized T] { 11 | def t(): T 12 | } 13 | 14 | class ChildBar[@specialized T](val t: T) extends ParentBar[T] 15 | 16 | def newChildBar(i: Int): ChildBar[Int] = new ChildBar(i) 17 | } 18 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/specialization/MethodReturnTypes.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.specialization 2 | 3 | object MethodReturnTypes { 4 | 5 | class Foo[T](t: T) 6 | 7 | object Foo { 8 | def create[T](t: T): Foo[T] = new Foo(t) 9 | def createSpecialized[@specialized T](t: T): Foo[T] = new Foo(t) 10 | } 11 | 12 | def boxed: Foo[Int] = Foo.create(1) 13 | 14 | def specialized: Foo[Int] = Foo.createSpecialized(1) 15 | } 16 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/specialization/Specialization.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.specialization 2 | 3 | object Specialization { 4 | 5 | case class ShareCount[@specialized(Long, Int) T](value: T) 6 | 7 | def newShareCount(l: Long): ShareCount[Long] = ShareCount(l) 8 | 9 | case class Foo[@specialized(Int, Long) X, @specialized(Int, Long) Y]( 10 | value: X, result: Y) 11 | 12 | def makeFoo: Foo[Int, Int] = Foo(1, 2).copy(value = 22) 13 | 14 | 15 | case class Bar[X, Y](value: X, result: Y) 16 | 17 | def makeBar: Bar[Int, Int] = Bar(1, 2) 18 | 19 | case class Defined(value: Int, result: Int) 20 | 21 | def makeDefined: Defined = Defined(1, 2).copy(value = 22) 22 | } 23 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/specialization/SpecializationBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.specialization 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations.Mode._ 6 | import org.openjdk.jmh.annotations._ 7 | 8 | import SpecializationBenchmark._ 9 | 10 | @BenchmarkMode(Array(Throughput)) 11 | @OutputTimeUnit(TimeUnit.SECONDS) 12 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 13 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 14 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 15 | class SpecializationBenchmark { 16 | 17 | @Benchmark 18 | def specialized(): Double = 19 | specializedExecution.shareCount.toDouble * specializedExecution.price 20 | 21 | @Benchmark 22 | def boxed(): Double = 23 | boxedExecution.shareCount.toDouble * boxedExecution.price 24 | } 25 | 26 | object SpecializationBenchmark { 27 | class SpecializedExecution[@specialized(Int) T1, @specialized(Double) T2]( 28 | val shareCount: Long, val price: Double) 29 | class BoxingExecution[T1, T2](val shareCount: T1, val price: T2) 30 | 31 | val specializedExecution: SpecializedExecution[Int, Double] = 32 | new SpecializedExecution(10l, 2d) 33 | val boxedExecution: BoxingExecution[Long, Double] = new BoxingExecution(10l, 2d) 34 | } 35 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/specialization/UnexpectedAllocations.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.specialization 2 | 3 | object UnexpectedAllocations { 4 | 5 | case class ShareCount(value: Int) extends AnyVal 6 | case class ExecutionCount(value: Int) 7 | 8 | class Container2[@specialized X, @specialized Y](x: X, y: Y) 9 | 10 | def shareCount = new Container2(ShareCount(1), 1) 11 | 12 | def executionCount = new Container2(ExecutionCount(1), 1) 13 | 14 | def ints = new Container2(1, 1) 15 | } 16 | -------------------------------------------------------------------------------- /chapter3/src/main/scala/highperfscala/tailrec/TailRecursion.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.tailrec 2 | 3 | import java.io.{BufferedReader, IOException} 4 | 5 | import scala.annotation.tailrec 6 | 7 | object TailRecursion { 8 | 9 | // This function cannot be marked @tailrec since the call to `sum` 10 | // is not the last instruction 11 | def sum(l: List[Int]): Int = l match { 12 | case Nil => 0 13 | case x :: xs => x + sum(xs) 14 | } 15 | 16 | def tailrecSum(l: List[Int]): Int = { 17 | @tailrec 18 | def loop(list: List[Int], acc: Int): Int = list match { 19 | case Nil => acc 20 | case x :: xs => loop(xs, acc + x) 21 | } 22 | loop(l, 0) 23 | } 24 | 25 | def sum2(l: List[Int]): Int = { 26 | 27 | def loop(list: List[Int], acc: Int): Int = list match { 28 | case Nil => acc 29 | case x :: xs => info(xs, acc + x) 30 | } 31 | 32 | def info(list: List[Int], acc: Int): Int = { 33 | println(s"${list.size} elements to examine. sum so far: $acc") 34 | loop(list, acc) 35 | } 36 | 37 | loop(l, 0) 38 | } 39 | 40 | def tailrecSum2(l: List[Int]): Int = { 41 | 42 | @tailrec 43 | def loop(list: List[Int], acc: Int): Int = list match { 44 | case Nil => acc 45 | case x :: xs => 46 | println(s"${list.size} elements to examine. sum so far: $acc") 47 | loop(list, acc) 48 | } 49 | 50 | loop(l, 0) 51 | } 52 | 53 | def sumFromReader(br: BufferedReader): Int = { 54 | 55 | def read(acc: Int, reader: BufferedReader): Int = { 56 | Option(reader.readLine().toInt) 57 | .fold(acc)(i => read(acc + i, reader)) 58 | } 59 | 60 | read(0, br) 61 | } 62 | 63 | def tailrecSumFromReader(br: BufferedReader): Int = { 64 | @tailrec 65 | def read(acc: Int, reader: BufferedReader): Int = { 66 | val opt = Option(reader.readLine().toInt) 67 | if (opt.isEmpty) acc else read(acc + opt.get, reader) 68 | } 69 | 70 | read(0, br) 71 | } 72 | 73 | class Printer(msg: String) { 74 | def printMessageNTimes(n: Int): Unit = { 75 | if (n > 0) { 76 | println(msg) 77 | printMessageNTimes(n - 1) 78 | } 79 | } 80 | } 81 | 82 | class TailRecPrinter(msg: String) { 83 | @tailrec 84 | final def printMessageNTimes(n: Int): Unit = { 85 | if (n > 0) { 86 | println(msg) 87 | printMessageNTimes(n - 1) 88 | } 89 | } 90 | } 91 | 92 | def tryCatchBlock(l: List[Int]): Int = { 93 | def loop(list: List[Int], acc: Int): Int = list match { 94 | case Nil => acc 95 | case x :: xs => 96 | try { 97 | loop(xs, acc + x) 98 | } catch { 99 | case e: IOException => 100 | println(s"Recursion got interrupted by exception") 101 | acc 102 | } 103 | } 104 | 105 | 106 | loop(l, 0) 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /chapter3/target/.history: -------------------------------------------------------------------------------- 1 | jm 2 | jmh 3 | jmh:run 4 | jmh:run .*LazyValBenchmarks -wi 3 -i 10 -f 1 -wbs 100000 -bs 100000 -jvmArgs "-Xmx1G -Xms1G" 5 | jmh:run .*LazyValBenchmarks -wi 10 -i 20 -f 1 -wbs 1000000 -bs 1000000 -jvmArgs "-Xmx1G -Xms1G" 6 | jmh:run 7 | jmh:run LambdasBenchmarks 8 | -------------------------------------------------------------------------------- /chapter3/target/streams/$global/clean/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter3/target/streams/$global/clean/$global/streams/out -------------------------------------------------------------------------------- /chapter3/target/streams/$global/ivyConfiguration/$global/streams/out: -------------------------------------------------------------------------------- 1 | [debug] Other repositories: 2 | [debug] Default repositories: 3 | [debug] Using inline dependencies specified in Scala. 4 | -------------------------------------------------------------------------------- /chapter3/target/streams/$global/ivySbt/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter3/target/streams/$global/ivySbt/$global/streams/out -------------------------------------------------------------------------------- /chapter3/target/streams/$global/projectDescriptors/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter3/target/streams/$global/projectDescriptors/$global/streams/out -------------------------------------------------------------------------------- /chapter4/src/main/resources/dataanalysis/executions: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter4/src/main/resources/dataanalysis/executions -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/dataanalysis/ListVectorExperiment.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis 2 | 3 | object ListVectorExperiment { 4 | 5 | def computeReturnsWithList( 6 | rollUp: MinuteRollUp, 7 | data: List[Midpoint]): List[Return] = { 8 | for { 9 | i <- (rollUp.value until data.size).toList 10 | } yield Return.fromMidpoint(data(i - rollUp.value), data(i)) 11 | } 12 | 13 | def computeReturnsWithVector( 14 | rollUp: MinuteRollUp, 15 | data: Vector[Midpoint]): Vector[Return] = { 16 | for { 17 | i <- (rollUp.value until data.size).toVector 18 | } yield Return.fromMidpoint(data(i - rollUp.value), data(i)) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/dataanalysis/MidpointSeries.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis 2 | 3 | import scala.annotation.tailrec 4 | 5 | class MidpointSeries private(val points: Vector[Midpoint]) extends AnyVal { 6 | 7 | def returns(rollUp: MinuteRollUp): Vector[Return] = { 8 | for { 9 | i <- (rollUp.value until points.size).toVector 10 | } yield Return.fromMidpoint(points(i - rollUp.value), points(i)) 11 | } 12 | 13 | def midpointAt(time: TimestampMinutes): Option[Midpoint] = { 14 | if (points.isEmpty || time.value < points.head.time.value || 15 | time.value > points.last.time.value) { 16 | None 17 | } else { 18 | val index = time.value - points.head.time.value 19 | Some(points(index)) 20 | } 21 | } 22 | 23 | private[dataanalysis] def size = points.size // provided for testing 24 | 25 | } 26 | 27 | object MidpointSeries { 28 | 29 | private def removeDuplicates(v: Vector[Midpoint]): Vector[Midpoint] = { 30 | 31 | @tailrec 32 | def loop( 33 | current: Midpoint, 34 | rest: Vector[Midpoint], 35 | result: Vector[Midpoint]): Vector[Midpoint] = { 36 | val sameTime = current +: rest.takeWhile(_.time == current.time) 37 | val average = sameTime.map(_.value).sum / sameTime.size 38 | 39 | val newResult = result :+ Midpoint(current.time, average) 40 | rest.drop(sameTime.size - 1) match { 41 | case h +: r => loop(h, r, newResult) 42 | case _ => newResult 43 | } 44 | } 45 | 46 | v match { 47 | case h +: rest => loop(h, rest, Vector.empty) 48 | case _ => Vector.empty 49 | } 50 | } 51 | 52 | private def extrapolate( 53 | a: Midpoint, 54 | b: Midpoint, 55 | time: TimestampMinutes): Midpoint = { 56 | val price = a.value + 57 | ((time.value - a.time.value) / (b.time.value - a.time.value)) * 58 | (b.value - a.value) 59 | Midpoint(time, price) 60 | } 61 | 62 | private def addMissingDataPoints(v: Vector[Midpoint]): Vector[Midpoint] = { 63 | @tailrec 64 | def loop( 65 | previous: Midpoint, 66 | rest: Vector[Midpoint], 67 | result: Vector[Midpoint]): Vector[Midpoint] = rest match { 68 | case current +: mPoints if previous.time.value == current.time.value - 1 => 69 | // Nothing to extrapolate, the data points are consecutive 70 | loop(current, mPoints, result :+ previous) 71 | 72 | case current +: mPoints if previous.time.value < current.time.value - 1 => 73 | //Need to generate a data point 74 | val newPoint = extrapolate(previous, current, previous.time.next) 75 | loop(newPoint, rest, result :+ previous) 76 | 77 | case _ => result :+ previous 78 | } 79 | 80 | v match { 81 | case h +: rest => loop(h, rest, Vector.empty) 82 | case _ => Vector.empty 83 | } 84 | } 85 | 86 | def fromExecution(executions: Vector[Execution]): MidpointSeries = { 87 | new MidpointSeries( 88 | addMissingDataPoints( 89 | removeDuplicates( 90 | executions.map(Midpoint.fromExecution)))) 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/dataanalysis/ReturnSeriesFrame.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis 2 | 3 | import org.saddle._ 4 | 5 | trait ReturnSeriesFrame 6 | 7 | class VectorBasedReturnSeriesFrame( 8 | val series: Vector[Vector[Return]]) extends ReturnSeriesFrame { 9 | 10 | lazy val scalingVector: Vector[Double] = 11 | for (i <- series.indices.toVector) yield series(i).max.value 12 | 13 | } 14 | 15 | class ArrayBasedReturnSeriesFrame( 16 | val series: Array[Array[Return]]) extends ReturnSeriesFrame { 17 | 18 | lazy val scalingVector: Array[Double] = { 19 | val v = new Array[Double](series.length) 20 | for (i <- series.indices) { 21 | v(i) = series(i).max.value 22 | } 23 | v 24 | } 25 | 26 | } 27 | 28 | class MatrixBasedReturnSeriesFrame( 29 | val series: Mat[Return]) extends ReturnSeriesFrame { 30 | 31 | val scalingVector: Array[Double] = { 32 | val v = new Array[Double](series.cols().size) 33 | for (i <- v.indices) { 34 | v(i) = series.col(i).toSeq.max.value 35 | } 36 | v 37 | } 38 | 39 | val scalingVec: Vec[Double] = Vec(scalingVector) 40 | 41 | } 42 | 43 | object ReturnSeriesFrame { 44 | 45 | def newArrayBasedFrame(series: Array[Array[Return]]): ArrayBasedReturnSeriesFrame = { 46 | new ArrayBasedReturnSeriesFrame(series) 47 | } 48 | 49 | def newMatrixBasedFrame(series: Array[Array[Return]]): MatrixBasedReturnSeriesFrame = { 50 | new MatrixBasedReturnSeriesFrame(Mat(series)) 51 | } 52 | 53 | def scaleVector(frame: VectorBasedReturnSeriesFrame): VectorBasedReturnSeriesFrame = { 54 | new VectorBasedReturnSeriesFrame( 55 | frame.series.zip(frame.scalingVector).map { case (series, scaling) => 56 | series.map(point => Return(point.value / scaling)) 57 | } 58 | ) 59 | } 60 | 61 | def scaleWithMap(frame: ArrayBasedReturnSeriesFrame): ArrayBasedReturnSeriesFrame = { 62 | new ArrayBasedReturnSeriesFrame( 63 | frame.series.zip(frame.scalingVector).map { case (series, scaling) => 64 | series.map(point => Return(point.value / scaling)) 65 | }) 66 | } 67 | 68 | def scaleWithSpire(frame: ArrayBasedReturnSeriesFrame): ArrayBasedReturnSeriesFrame = { 69 | import spire.syntax.cfor._ 70 | 71 | val result = new Array[Array[Return]](frame.series.length) 72 | 73 | cfor(0)(_ < frame.series.length, _ + 1) { i => 74 | val s = frame.series(i) 75 | val scaled = new Array[Return](s.length) 76 | cfor(0)(_ < s.length, _ + 1) { j => 77 | val point = s(j) 78 | scaled(j) = Return(point.value / frame.scalingVector(i)) 79 | } 80 | result(i) = scaled 81 | } 82 | 83 | new ArrayBasedReturnSeriesFrame(result) 84 | } 85 | 86 | def scaleWithSaddle(frame: MatrixBasedReturnSeriesFrame): MatrixBasedReturnSeriesFrame = 87 | new MatrixBasedReturnSeriesFrame( 88 | (frame.series.map(_.value) dot frame.scalingVec).map(Return.apply) 89 | ) 90 | 91 | } 92 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/dataanalysis/api.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis 2 | 3 | import org.joda.time.DateTime 4 | 5 | case class TimestampMinutes(value: Int) extends AnyVal { 6 | 7 | def next: TimestampMinutes = TimestampMinutes(value + 1) 8 | } 9 | object TimestampMinutes { 10 | def fromDateTime(dt: DateTime): TimestampMinutes = TimestampMinutes( 11 | (dt.withSecondOfMinute(0).withMillisOfSecond(0) 12 | .getMillis / (1000 * 60)).toInt) 13 | } 14 | 15 | case class AskPrice(value: Int) extends AnyVal 16 | 17 | case class BidPrice(value: Int) extends AnyVal 18 | 19 | case class Execution(time: TimestampMinutes, ask: AskPrice, bid: BidPrice) 20 | 21 | case class Midpoint(time: TimestampMinutes, value: Double) 22 | object Midpoint { 23 | 24 | def fromAskAndBid( 25 | time: TimestampMinutes, 26 | askPrice: AskPrice, 27 | bidPrice: BidPrice): Midpoint = 28 | Midpoint(time, (bidPrice.value + askPrice.value) / 2D) 29 | 30 | def fromExecution(ex: Execution): Midpoint = 31 | fromAskAndBid(ex.time, ex.ask, ex.bid) 32 | 33 | } 34 | 35 | case class MinuteRollUp(value: Int) extends AnyVal 36 | 37 | case class Return(value: Double) extends AnyVal 38 | 39 | object Return { 40 | def fromMidpoint(start: Midpoint, end: Midpoint): Return = 41 | Return((end.value - start.value) / start.value * 100) 42 | 43 | implicit val returnOrdering = new Ordering[Return] { 44 | override def compare(x: Return, y: Return): Int = 45 | x.value.compare(y.value) 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/dataanalysis/benchmarks/ListVectorBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis.benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import highperfscala.dataanalysis.benchmarks.ListVectorBenchmarks.ExecutionList 6 | import highperfscala.dataanalysis.util.DataCodec 7 | import highperfscala.dataanalysis.{ListVectorExperiment, Midpoint, MinuteRollUp, Return} 8 | import org.openjdk.jmh.annotations.Mode._ 9 | import org.openjdk.jmh.annotations.{BenchmarkMode, _} 10 | 11 | @BenchmarkMode(Array(Throughput)) 12 | @OutputTimeUnit(TimeUnit.SECONDS) 13 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 14 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 15 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 16 | class ListVectorBenchmarks { 17 | 18 | @Benchmark 19 | def computeReturnsWithList(s: ExecutionList): List[Return] = { 20 | ListVectorExperiment.computeReturnsWithList( 21 | MinuteRollUp(s.rollUp), 22 | s.list 23 | ) 24 | } 25 | 26 | @Benchmark 27 | def computeReturnsWithVector(s: ExecutionList): Vector[Return] = { 28 | ListVectorExperiment.computeReturnsWithVector( 29 | MinuteRollUp(s.rollUp), 30 | s.vector 31 | ) 32 | } 33 | 34 | } 35 | 36 | object ListVectorBenchmarks { 37 | 38 | @State(Scope.Benchmark) 39 | class ExecutionList { 40 | 41 | @Param(Array("10", "60", "120")) 42 | var rollUp: Int = 0 43 | 44 | var list: List[Midpoint] = Nil 45 | var vector: Vector[Midpoint] = Vector.empty 46 | 47 | @Setup(Level.Trial) 48 | def setup(): Unit = { 49 | list = DataCodec.read( 50 | getClass.getResourceAsStream("/dataanalysis/executions")) 51 | .map(Midpoint.fromExecution) 52 | vector = list.toVector 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/dataanalysis/benchmarks/ReturnSeriesFrameBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis.benchmarks 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import highperfscala.dataanalysis._ 6 | import highperfscala.dataanalysis.benchmarks.ReturnSeriesFrameBenchmarks.SeriesState 7 | import org.openjdk.jmh.annotations._ 8 | import org.openjdk.jmh.annotations.Mode._ 9 | 10 | import scala.util.Random 11 | 12 | @BenchmarkMode(Array(Throughput)) 13 | @OutputTimeUnit(TimeUnit.SECONDS) 14 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 15 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 16 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 17 | class ReturnSeriesFrameBenchmarks { 18 | 19 | @Benchmark 20 | def normalizeVectors(s: SeriesState): VectorBasedReturnSeriesFrame = { 21 | ReturnSeriesFrame.scaleVector(s.vectorBased) 22 | } 23 | 24 | @Benchmark 25 | def normalizeArray(s: SeriesState): ArrayBasedReturnSeriesFrame = { 26 | ReturnSeriesFrame.scaleWithMap(s.arrayBased) 27 | } 28 | 29 | @Benchmark 30 | def normalizeSpireLoop(s: SeriesState): ArrayBasedReturnSeriesFrame = { 31 | ReturnSeriesFrame.scaleWithSpire(s.arrayBased) 32 | } 33 | 34 | @Benchmark 35 | def normalizeSaddleMatrix(s: SeriesState): MatrixBasedReturnSeriesFrame = { 36 | ReturnSeriesFrame.scaleWithSaddle(s.matrixBased) 37 | } 38 | 39 | } 40 | 41 | object ReturnSeriesFrameBenchmarks { 42 | 43 | @State(Scope.Benchmark) 44 | class SeriesState { 45 | 46 | @Param(Array("10")) 47 | var seriesCount: Int = 0 48 | 49 | @Param(Array(/*"60", "1440",*/ "300000")) 50 | var seriesSize: Int = 0 51 | 52 | var vectorBased: VectorBasedReturnSeriesFrame = null 53 | var arrayBased: ArrayBasedReturnSeriesFrame = null 54 | var matrixBased: MatrixBasedReturnSeriesFrame = null 55 | 56 | @Setup(Level.Trial) 57 | def setup(): Unit = { 58 | val series = new Array[Array[Return]](seriesCount) 59 | for(i <- 0 until seriesCount){ 60 | val localSeries = new Array[Return](seriesSize) 61 | for(j <- 0 until seriesSize){ 62 | localSeries(j) = Return(Random.nextInt(100)) 63 | } 64 | series(i) = localSeries 65 | } 66 | 67 | val vectors = series.map(_.toVector).toVector 68 | 69 | vectorBased = new VectorBasedReturnSeriesFrame(vectors) 70 | arrayBased = ReturnSeriesFrame.newArrayBasedFrame(series) 71 | matrixBased = ReturnSeriesFrame.newMatrixBasedFrame(series) 72 | vectorBased.scalingVector 73 | arrayBased.scalingVector 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/dataanalysis/util/DataCodec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis.util 2 | 3 | import java.io._ 4 | 5 | import highperfscala.dataanalysis.Execution 6 | import org.slf4s.Logging 7 | 8 | import scala.collection.mutable.ListBuffer 9 | 10 | object DataCodec extends Logging { 11 | 12 | def write(cs: List[Execution], output: File): Unit = { 13 | val oos = new ObjectOutputStream(new FileOutputStream(output)) 14 | cs.foreach(oos.writeObject) 15 | oos.close() 16 | } 17 | 18 | def read(input: File): List[Execution] = { 19 | val fis = new FileInputStream(input) 20 | 21 | val ois = new ObjectInputStream(fis) 22 | val commandBuilder = ListBuffer[Execution]() 23 | while(fis.available() != 0) { 24 | commandBuilder.append(ois.readObject().asInstanceOf[Execution]) 25 | } 26 | ois.close() 27 | fis.close() 28 | 29 | commandBuilder.result() 30 | } 31 | 32 | def read(input: InputStream): List[Execution] = { 33 | val ois = new ObjectInputStream(input) 34 | val commandBuilder = ListBuffer[Execution]() 35 | while(input.available() != 0) { 36 | commandBuilder.append(ois.readObject().asInstanceOf[Execution]) 37 | } 38 | ois.close() 39 | 40 | commandBuilder.result() 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/dataanalysis/util/DataGenerator.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis.util 2 | 3 | import highperfscala.dataanalysis._ 4 | import org.joda.time.DateTime 5 | 6 | object DataGenerator { 7 | 8 | import java.io.File 9 | 10 | import org.scalacheck.Gen 11 | 12 | import scala.util.Random 13 | 14 | private def genExecution(time: DateTime): Gen[Execution] = for { 15 | bid <- Gen.chooseNum(1, 100) 16 | ask <- Gen.chooseNum(bid, bid+20) 17 | et = TimestampMinutes.fromDateTime(time) 18 | } yield Execution(et, AskPrice(ask), BidPrice(bid)) 19 | 20 | def main(args: Array[String]): Unit = { 21 | 22 | val outputFile = new File(args(0)) 23 | val minutes = args(1).toInt 24 | 25 | val startDt = DateTime.now().minusMinutes(minutes) 26 | 27 | val xs = for { 28 | i <- 0 to minutes 29 | time = startDt.plusMinutes(i) 30 | execCount = Random.nextInt(5) + 1 31 | } yield Gen.listOfN(execCount, genExecution(time)).sample.get 32 | 33 | DataCodec.write(xs.flatten.toList, outputFile) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/features/FeatureGeneration.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.features 2 | 3 | import highperfscala.orderbook.Events.OrderExecuted 4 | import highperfscala.orderbook.{EventInstant, Execution} 5 | 6 | import scalaz.{@@, Tag} 7 | 8 | object FeatureGeneration { 9 | 10 | case class MidpointPrice(value: BigDecimal) extends AnyVal 11 | object MidpointPrice { 12 | def create(buy: Execution, sell: Execution): MidpointPrice = 13 | MidpointPrice((buy.price.value + sell.price.value) / BigDecimal(2)) 14 | } 15 | case class EventSeconds(value: Long) extends AnyVal 16 | object EventSeconds { 17 | def create(i: EventInstant): EventSeconds = 18 | EventSeconds(i.value.getMillis / 1000l) 19 | } 20 | 21 | sealed trait PerSecond 22 | def midpointPerSecond(p: MidpointPrice): MidpointPrice @@ PerSecond = Tag(p) 23 | 24 | // Illustrate example with fold to show the principle of monoid 25 | 26 | def foo(xs: List[OrderExecuted]) = { 27 | val secondlyPrices = xs.map(e => e.i -> MidpointPrice.create(e.buy, e.sell)) 28 | .groupBy(z => EventSeconds.create(z._1)) 29 | // What about missing indices? 30 | .mapValues(prices => midpointPerSecond(MidpointPrice( 31 | prices.map(_._2).reduceOption((x, y) => 32 | MidpointPrice(x.value + y.value)).getOrElse(MidpointPrice( 33 | BigDecimal(0))).value / prices.size))) 34 | .toList.sortWith((x, y) => x._1.value <= y._1.value) 35 | .map(_._2) 36 | 37 | secondlyPrices.sliding(size = 6) 38 | secondlyPrices.sliding(size = 11) 39 | secondlyPrices.sliding(size = 21) 40 | 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/CancelBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package orderbook 3 | 4 | import java.util.concurrent.TimeUnit 5 | 6 | import highperfscala.orderbook.Commands._ 7 | import highperfscala.orderbook.Events.Event 8 | import org.openjdk.jmh.annotations.Mode.Throughput 9 | import org.openjdk.jmh.annotations._ 10 | 11 | @BenchmarkMode(Array(Throughput)) 12 | @OutputTimeUnit(TimeUnit.SECONDS) 13 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 14 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 15 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 16 | class CancelBenchmarks { 17 | import CancelBenchmarks._ 18 | 19 | @Benchmark 20 | def eagerCancelLastOrderInLine(b: BookWithLargeQueue): (QueueOrderBook, Event) = 21 | QueueOrderBook.handle(systemEventTime, b.eagerBook, b.cancelLast) 22 | 23 | @Benchmark 24 | def eagerCancelFirstOrderInLine(b: BookWithLargeQueue): (QueueOrderBook, Event) = 25 | QueueOrderBook.handle(systemEventTime, b.eagerBook, b.cancelFirst) 26 | 27 | @Benchmark 28 | def eagerCancelNonexistentOrder(b: BookWithLargeQueue): (QueueOrderBook, Event) = 29 | QueueOrderBook.handle(systemEventTime, b.eagerBook, b.cancelNonexistent) 30 | 31 | @Benchmark 32 | def lazyCancelLastOrderInLine(b: BookWithLargeQueue): (LazyCancelOrderBook, Event) = 33 | LazyCancelOrderBook.handle(systemEventTime, b.lazyBook, b.cancelLast) 34 | 35 | @Benchmark 36 | def lazyCancelFirstOrderInLine(b: BookWithLargeQueue): (LazyCancelOrderBook, Event) = 37 | LazyCancelOrderBook.handle(systemEventTime, b.lazyBook, b.cancelFirst) 38 | 39 | @Benchmark 40 | def lazyCancelNonexistentOrder(b: BookWithLargeQueue): (LazyCancelOrderBook, Event) = 41 | LazyCancelOrderBook.handle(systemEventTime, b.lazyBook, b.cancelNonexistent) 42 | } 43 | 44 | object CancelBenchmarks { 45 | 46 | val systemEventTime = () => EventInstant.now() 47 | 48 | @State(Scope.Benchmark) 49 | class BookWithLargeQueue { 50 | private val p = Price(BigDecimal(1.00)) 51 | private val firstId: Int = 1 52 | private val defaultCancelLast = CancelOrder( 53 | CommandInstant.now(), OrderId(-1)) 54 | 55 | @Param(Array("1", "10")) 56 | var enqueuedOrderCount: Int = 0 57 | 58 | var eagerBook: QueueOrderBook = QueueOrderBook.empty 59 | 60 | var lazyBook: LazyCancelOrderBook = LazyCancelOrderBook.empty 61 | 62 | @Setup(Level.Trial) 63 | def setup(): Unit = { 64 | if (enqueuedOrderCount < 0) 65 | sys.error(s"Invalid enqueued order count = $enqueuedOrderCount") 66 | assert(eagerBook == QueueOrderBook.empty) 67 | assert(lazyBook == LazyCancelOrderBook.empty) 68 | assert(cancelLast == defaultCancelLast) 69 | 70 | cancelLast = CancelOrder( 71 | CommandInstant.now(), OrderId(enqueuedOrderCount)) 72 | eagerBook = { 73 | (firstId to enqueuedOrderCount).foldLeft(QueueOrderBook.empty) { 74 | case (ob, i) => 75 | QueueOrderBook.handle( 76 | () => EventInstant.now(), 77 | ob, AddLimitOrder( 78 | CommandInstant.now(), BuyLimitOrder(OrderId(i), p)))._1 79 | } 80 | } 81 | lazyBook = { 82 | (firstId to enqueuedOrderCount).foldLeft(LazyCancelOrderBook.empty) { 83 | case (ob, i) => 84 | LazyCancelOrderBook.handle( 85 | () => EventInstant.now(), 86 | ob, AddLimitOrder( 87 | CommandInstant.now(), BuyLimitOrder(OrderId(i), p)))._1 88 | } 89 | } 90 | 91 | assert(cancelLast != defaultCancelLast) 92 | if (enqueuedOrderCount > 0) 93 | assert(eagerBook.bids.head._2.size == enqueuedOrderCount, 94 | s"Book built incorrectly! Expected book to contain " + 95 | s"$enqueuedOrderCount bids for $p, but actual book is $eagerBook") 96 | } 97 | 98 | var cancelLast: CancelOrder = defaultCancelLast 99 | val cancelFirst: CancelOrder = CancelOrder( 100 | CommandInstant.now(), OrderId(firstId)) 101 | val cancelNonexistent: CancelOrder = CancelOrder( 102 | CommandInstant.now(), OrderId(-1)) 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/InterleavedOrderBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import highperfscala.orderbook.Commands.{CancelOrder, AddLimitOrder} 6 | import org.openjdk.jmh.annotations.Mode.Throughput 7 | import org.openjdk.jmh.annotations._ 8 | import InterleavedOrderBenchmarks._ 9 | 10 | @BenchmarkMode(Array(Throughput)) 11 | @OutputTimeUnit(TimeUnit.SECONDS) 12 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 13 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 14 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 15 | class InterleavedOrderBenchmarks { 16 | 17 | @Benchmark 18 | def eagerOneToOneCT(state: InterleavedOrderState): QueueOrderBook = { 19 | val b1 = QueueOrderBook.handle(() => EventInstant.now(), 20 | state.eagerBook, firstCancel)._1 21 | QueueOrderBook.handle(() => EventInstant.now(), 22 | b1, firstCrossSell)._1 23 | } 24 | 25 | @Benchmark 26 | def lazyOneToOneCT(state: InterleavedOrderState): LazyCancelOrderBook = { 27 | val b1 = LazyCancelOrderBook.handle(() => EventInstant.now(), 28 | state.lazyBook, firstCancel)._1 29 | LazyCancelOrderBook.handle(() => EventInstant.now(), 30 | b1, firstCrossSell)._1 31 | } 32 | 33 | @Benchmark 34 | def eagerTwoToOneCT(state: InterleavedOrderState): QueueOrderBook = { 35 | val b1 = QueueOrderBook.handle(() => EventInstant.now(), 36 | state.eagerBook, firstCancel)._1 37 | val b2 = QueueOrderBook.handle(() => EventInstant.now(), 38 | b1, secondCancel)._1 39 | QueueOrderBook.handle(() => EventInstant.now(), 40 | b2, firstCrossSell)._1 41 | } 42 | 43 | @Benchmark 44 | def lazyTwoToOneCT(state: InterleavedOrderState): LazyCancelOrderBook = { 45 | val b1 = LazyCancelOrderBook.handle(() => EventInstant.now(), 46 | state.lazyBook, firstCancel)._1 47 | val b2 = LazyCancelOrderBook.handle(() => EventInstant.now(), 48 | b1, secondCancel)._1 49 | LazyCancelOrderBook.handle(() => EventInstant.now(), 50 | b2, firstCrossSell)._1 51 | } 52 | 53 | @Benchmark 54 | def eagerOneToTwoCT(state: InterleavedOrderState): QueueOrderBook = { 55 | val b1 = QueueOrderBook.handle(() => EventInstant.now(), 56 | state.eagerBook, firstCancel)._1 57 | val b2 = QueueOrderBook.handle(() => EventInstant.now(), 58 | b1, firstCrossSell)._1 59 | QueueOrderBook.handle(() => EventInstant.now(), 60 | b2, secondCrossSell)._1 61 | } 62 | 63 | @Benchmark 64 | def lazyOneToTwoCT(state: InterleavedOrderState): LazyCancelOrderBook = { 65 | val b1 = LazyCancelOrderBook.handle(() => EventInstant.now(), 66 | state.lazyBook, firstCancel)._1 67 | val b2 = LazyCancelOrderBook.handle(() => EventInstant.now(), 68 | b1, firstCrossSell)._1 69 | LazyCancelOrderBook.handle(() => EventInstant.now(), 70 | b2, secondCrossSell)._1 71 | } 72 | } 73 | 74 | object InterleavedOrderBenchmarks { 75 | private val bidPrice = Price(BigDecimal(5)) 76 | private val maxOrderCount = 30 77 | 78 | val firstCancel: CancelOrder = CancelOrder(CommandInstant.now(), OrderId(1)) 79 | val secondCancel: CancelOrder = CancelOrder(CommandInstant.now(), OrderId(2)) 80 | val firstCrossSell: AddLimitOrder = AddLimitOrder(CommandInstant.now(), 81 | SellLimitOrder(OrderId(maxOrderCount + 1), Price(bidPrice.value - 1))) 82 | val secondCrossSell: AddLimitOrder = AddLimitOrder(CommandInstant.now(), 83 | SellLimitOrder(OrderId(maxOrderCount + 2), Price(bidPrice.value - 1))) 84 | 85 | @State(Scope.Benchmark) 86 | class InterleavedOrderState { 87 | var lazyBook: LazyCancelOrderBook = LazyCancelOrderBook.empty 88 | var eagerBook: QueueOrderBook = QueueOrderBook.empty 89 | 90 | @Setup 91 | def setup(): Unit = { 92 | lazyBook = (1 to maxOrderCount).foldLeft(LazyCancelOrderBook.empty) { 93 | case (b, i) => LazyCancelOrderBook.handle( 94 | () => EventInstant.now(), b, AddLimitOrder( 95 | CommandInstant.now(), BuyLimitOrder(OrderId(i), bidPrice)))._1 96 | } 97 | eagerBook = (1 to maxOrderCount).foldLeft(QueueOrderBook.empty) { 98 | case (b, i) => QueueOrderBook.handle( 99 | () => EventInstant.now(), b, AddLimitOrder( 100 | CommandInstant.now(), BuyLimitOrder(OrderId(i), bidPrice)))._1 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/LatencyBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import java.io.File 4 | 5 | import highperfscala.orderbook.util.DataCodec 6 | import org.mpierce.metrics.reservoir.hdrhistogram.HdrHistogramReservoir 7 | import util._ 8 | 9 | object LatencyBenchmark { 10 | 11 | sealed trait BookImplementation 12 | case object QueueImplementation extends BookImplementation 13 | case object LazyImplementation extends BookImplementation 14 | object BookImplementation { 15 | def fromString(s: String): Option[BookImplementation] = 16 | s.toLowerCase() match { 17 | case "lazy" => Some(LazyImplementation) 18 | case "queue" => Some(QueueImplementation) 19 | case _ => None 20 | } 21 | } 22 | 23 | def main(args: Array[String]): Unit = { 24 | 25 | val commandSample = DataCodec.read(new File(args(0))) 26 | val (commandsPerSecond, iterations) = (args(1).toInt, args(2).toInt) 27 | val bookImplementation = BookImplementation.fromString(args(3)).fold( 28 | sys.error(s"Unsupported book implementation = ${args(3)}"))(identity) 29 | 30 | val totalCommandCount = commandsPerSecond * iterations 31 | 32 | // Circular dependency with BookImplementation 33 | jvmWarmUp(commandSample, bookImplementation) 34 | 35 | val histogram = new HdrHistogramReservoir() 36 | 37 | var commandsWithOffset = 38 | generateCount(commandSample, totalCommandCount) 39 | .grouped(commandsPerSecond) 40 | .toList.zipWithIndex 41 | .flatMap { 42 | case (secondBatch, sBatchIndex) => 43 | val batchOffsetInMs = sBatchIndex * 1000 44 | val commandIntervalInMs = 1000.0 / commandsPerSecond 45 | secondBatch.zipWithIndex.map { 46 | case (command, commandIndex) => 47 | val commandOffsetInMs = 48 | Math.floor(commandIntervalInMs * commandIndex).toInt 49 | (command, batchOffsetInMs + commandOffsetInMs) 50 | } 51 | } 52 | 53 | val systemEventTime = () => EventInstant.now() 54 | val testStart = System.currentTimeMillis() 55 | 56 | // This is terrible duplication 57 | bookImplementation match { 58 | case LazyImplementation => 59 | var book = LazyCancelOrderBook.empty 60 | 61 | while (commandsWithOffset.nonEmpty) { 62 | val (command, offsetInMs) = commandsWithOffset.head 63 | val shouldStart = testStart + offsetInMs 64 | 65 | while (shouldStart > System.currentTimeMillis()) { 66 | // keep the thread busy while waiting for the next batch to be sent 67 | } 68 | 69 | book = LazyCancelOrderBook.handle(systemEventTime, book, command)._1 70 | val end = System.currentTimeMillis() 71 | // record latency 72 | histogram.update(end - shouldStart) 73 | 74 | commandsWithOffset = commandsWithOffset.tail 75 | } 76 | 77 | printSnapshot(histogram.getSnapshot) 78 | case QueueImplementation => 79 | var book = QueueOrderBook.empty 80 | 81 | while (commandsWithOffset.nonEmpty) { 82 | val (command, offsetInMs) = commandsWithOffset.head 83 | val shouldStart = testStart + offsetInMs 84 | 85 | while (shouldStart > System.currentTimeMillis()) { 86 | // keep the thread busy while waiting for the next batch to be sent 87 | } 88 | 89 | book = QueueOrderBook.handle(systemEventTime, book, command)._1 90 | val end = System.currentTimeMillis() 91 | // record latency 92 | histogram.update(end - shouldStart) 93 | 94 | commandsWithOffset = commandsWithOffset.tail 95 | } 96 | 97 | printSnapshot(histogram.getSnapshot) 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/LazyCancelOrderBook.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package orderbook 3 | 4 | import highperfscala.orderbook.Commands._ 5 | import highperfscala.orderbook.Events._ 6 | 7 | import scala.annotation.tailrec 8 | import scala.collection.immutable.{Queue, TreeMap} 9 | 10 | // http://web.archive.org/web/20110312023826/http://www.quantcup.org/home/howtohft_howtobuildafastlimitorderbook 11 | case class LazyCancelOrderBook( 12 | activeIds: Set[OrderId], 13 | pendingCancelIds: Set[OrderId], 14 | bids: TreeMap[Price, Queue[BuyLimitOrder]], 15 | offers: TreeMap[Price, Queue[SellLimitOrder]]) { 16 | def bestBid: Option[BuyLimitOrder] = bids.lastOption.flatMap(_._2.headOption) 17 | def bestOffer: Option[SellLimitOrder] = 18 | offers.headOption.flatMap(_._2.headOption) 19 | } 20 | 21 | object LazyCancelOrderBook { 22 | val noEvent: Option[Event] = None 23 | val empty: LazyCancelOrderBook = LazyCancelOrderBook( 24 | Set.empty, 25 | Set.empty, 26 | TreeMap.empty[Price, Queue[BuyLimitOrder]], 27 | TreeMap.empty[Price, Queue[SellLimitOrder]]) 28 | 29 | // Could make sense to handle with State monad 30 | def handle( 31 | currentTime: () => EventInstant, 32 | ob: LazyCancelOrderBook, 33 | c: Command): (LazyCancelOrderBook, Event) = c match { 34 | case AddLimitOrder(_, o) => handleAddLimitOrder(currentTime, ob, o) 35 | case CancelOrder(_, id) => handleCancelOrder(currentTime, ob, id) 36 | } 37 | 38 | // Am cheating by adding order to book if current level does not have active 39 | // order 40 | private def handleAddLimitOrder( 41 | currentTime: () => EventInstant, 42 | ob: LazyCancelOrderBook, 43 | lo: LimitOrder): (LazyCancelOrderBook, Event) = lo match { 44 | case b: BuyLimitOrder => 45 | @tailrec 46 | def findActiveOrder( 47 | q: Queue[SellLimitOrder], 48 | idsToRemove: Set[OrderId]): (Option[SellLimitOrder], Option[Queue[SellLimitOrder]], Set[OrderId]) = 49 | q.dequeueOption match { 50 | case Some((o, qq)) => ob.pendingCancelIds.contains(o.id) match { 51 | case true => 52 | findActiveOrder(qq, idsToRemove + o.id) 53 | case false => 54 | (Some(o), if (qq.nonEmpty) Some(qq) else None, idsToRemove + o.id) 55 | } 56 | case None => (None, None, idsToRemove) 57 | } 58 | 59 | def restLimitOrder: (LazyCancelOrderBook, Event) = { 60 | val orders = ob.bids.getOrElse(b.price, Queue.empty) 61 | ob.copy(bids = ob.bids + (b.price -> orders.enqueue(b)), 62 | activeIds = ob.activeIds + b.id) -> LimitOrderAdded(currentTime()) 63 | } 64 | 65 | ob.bestOffer.exists(_.price.value <= b.price.value) match { 66 | case true => ob.offers.headOption.fold(restLimitOrder) { 67 | case (p, q) => findActiveOrder(q, Set.empty) match { 68 | case (Some(o), Some(qq), rms) => (ob.copy( 69 | offers = ob.offers + (o.price -> qq), 70 | activeIds = ob.activeIds -- rms), OrderExecuted(currentTime(), 71 | Execution(b.id, o.price), Execution(o.id, o.price))) 72 | case (Some(o), None, rms) => (ob.copy( 73 | offers = ob.offers - o.price, activeIds = ob.activeIds -- rms), 74 | OrderExecuted(currentTime(), 75 | Execution(b.id, o.price), Execution(o.id, o.price))) 76 | case (None, _, rms) => 77 | val bs = ob.bids.getOrElse(b.price, Queue.empty).enqueue(b) 78 | (ob.copy(bids = ob.bids + (b.price -> bs), 79 | offers = ob.offers - p, 80 | activeIds = ob.activeIds -- rms + b.id), 81 | LimitOrderAdded(currentTime())) 82 | } 83 | } 84 | case false => restLimitOrder 85 | } 86 | 87 | case s: SellLimitOrder => 88 | @tailrec 89 | def findActiveOrder( 90 | q: Queue[BuyLimitOrder], 91 | idsToRemove: Set[OrderId]): (Option[BuyLimitOrder], Option[Queue[BuyLimitOrder]], Set[OrderId]) = 92 | q.dequeueOption match { 93 | case Some((o, qq)) => ob.pendingCancelIds.contains(o.id) match { 94 | case true => 95 | findActiveOrder(qq, idsToRemove + o.id) 96 | case false => 97 | (Some(o), if (qq.nonEmpty) Some(qq) else None, idsToRemove + o.id) 98 | } 99 | case None => (None, None, idsToRemove) 100 | } 101 | 102 | def restLimitOrder: (LazyCancelOrderBook, Event) = { 103 | val orders = ob.offers.getOrElse(s.price, Queue.empty) 104 | ob.copy(offers = ob.offers + (s.price -> orders.enqueue(s)), 105 | activeIds = ob.activeIds + s.id) -> 106 | LimitOrderAdded(currentTime()) 107 | } 108 | 109 | ob.bestBid.exists(_.price.value >= s.price.value) match { 110 | case true => ob.bids.headOption.fold(restLimitOrder) { 111 | case (p, q) => findActiveOrder(q, Set.empty) match { 112 | case (Some(o), Some(qq), rms) => (ob.copy( 113 | bids = ob.bids + (o.price -> qq), activeIds = ob.activeIds -- rms), 114 | OrderExecuted(currentTime(), 115 | Execution(o.id, o.price), Execution(s.id, o.price))) 116 | 117 | case (Some(o), None, rms) => (ob.copy( 118 | bids = ob.bids - o.price, activeIds = ob.activeIds -- rms), 119 | OrderExecuted(currentTime(), 120 | Execution(o.id, o.price), Execution(s.id, o.price))) 121 | 122 | // If no order found, implies that the queue is now empty 123 | case (None, _, rms) => 124 | val os = ob.offers.getOrElse(s.price, Queue.empty).enqueue(s) 125 | (ob.copy(offers = ob.offers + (s.price -> os), 126 | bids = ob.bids - p, activeIds = ob.activeIds -- rms + s.id), 127 | LimitOrderAdded(currentTime())) 128 | } 129 | } 130 | case false => restLimitOrder 131 | } 132 | } 133 | 134 | private def handleCancelOrder( 135 | currentTime: () => EventInstant, 136 | ob: LazyCancelOrderBook, 137 | id: OrderId): (LazyCancelOrderBook, Event) = 138 | ob.activeIds.contains(id) match { 139 | case true => ob.copy(activeIds = ob.activeIds - id, 140 | pendingCancelIds = ob.pendingCancelIds + id) -> 141 | OrderCanceled(currentTime(), id) 142 | case false => ob -> OrderCancelRejected(currentTime(), id) 143 | } 144 | } 145 | 146 | 147 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/ListOrderBook.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package orderbook 3 | 4 | import orderbook.Commands._ 5 | import orderbook.Events._ 6 | 7 | import scala.collection.immutable.TreeMap 8 | 9 | // http://web.archive.org/web/20110312023826/http://www.quantcup.org/home/howtohft_howtobuildafastlimitorderbook 10 | case class ListOrderBook( 11 | bids: TreeMap[Price, List[BuyLimitOrder]], 12 | offers: TreeMap[Price, List[SellLimitOrder]]) { 13 | def bestBid: Option[BuyLimitOrder] = bids.lastOption.flatMap(_._2.headOption) 14 | def bestOffer: Option[SellLimitOrder] = 15 | offers.headOption.flatMap(_._2.headOption) 16 | } 17 | 18 | object ListOrderBook { 19 | val noEvent: Option[Event] = None 20 | val empty: ListOrderBook = ListOrderBook( 21 | TreeMap.empty[Price, List[BuyLimitOrder]], 22 | TreeMap.empty[Price, List[SellLimitOrder]]) 23 | 24 | // Could make sense to handle with State monad 25 | def handle( 26 | currentTime: () => EventInstant, 27 | ob: ListOrderBook, 28 | c: Command): (ListOrderBook, Event) = c match { 29 | case AddLimitOrder(_, o) => handleAddLimitOrder(currentTime, ob, o) 30 | case CancelOrder(_, id) => handleCancelOrder(currentTime, ob, id) 31 | } 32 | 33 | private def handleAddLimitOrder( 34 | currentTime: () => EventInstant, 35 | ob: ListOrderBook, 36 | o: LimitOrder): (ListOrderBook, Event) = o match { 37 | case oo: BuyLimitOrder => 38 | ob.bestOffer.exists(oo.price.value >= _.price.value) match { 39 | case true => crossBookBuy(currentTime, ob, oo) 40 | case false => 41 | val orders = ob.bids.getOrElse(oo.price, Nil) 42 | ob.copy(bids = ob.bids + (oo.price -> orders.:+(oo))) -> 43 | LimitOrderAdded(currentTime()) 44 | } 45 | case oo: SellLimitOrder => 46 | ob.bestBid.exists(oo.price.value <= _.price.value) match { 47 | case true => crossBookSell(currentTime, ob, oo) 48 | case false => 49 | val orders = ob.offers.getOrElse(oo.price, Nil) 50 | ob.copy(offers = ob.offers + (oo.price -> orders.:+(oo))) -> 51 | LimitOrderAdded(currentTime()) 52 | } 53 | } 54 | 55 | private def handleCancelOrder( 56 | currentTime: () => EventInstant, 57 | ob: ListOrderBook, 58 | id: OrderId): (ListOrderBook, Event) = { 59 | ob.bids.find { case (p, q) => q.exists(_.id == id) }.fold( 60 | ob.offers.find { case (p, q) => q.exists(_.id == id) } 61 | .fold[(ListOrderBook, Event)](ob -> 62 | OrderCancelRejected(currentTime(), id)) { 63 | case (p, q) => 64 | // It's awkward to duplicate the queue remove logic, but the different 65 | // types for bid vs offer make it difficult to share the code 66 | val updatedQ = q.filter(_.id != id) 67 | ob.copy(offers = updatedQ.nonEmpty match { 68 | case true => ob.offers + (p -> updatedQ) 69 | case false => ob.offers - p 70 | }) -> OrderCanceled(currentTime(), id) 71 | }) { case (p, q) => 72 | val updatedQ = q.filter(_.id != id) 73 | ob.copy(bids = updatedQ.nonEmpty match { 74 | case true => ob.bids + (p -> updatedQ) 75 | case false => ob.bids - p 76 | }) -> OrderCanceled(currentTime(), id) 77 | } 78 | } 79 | 80 | private def crossBookSell( 81 | currentTime: () => EventInstant, 82 | ob: ListOrderBook, 83 | s: SellLimitOrder): (ListOrderBook, Event) = 84 | ob.bids.lastOption.fold(handleAddLimitOrder(currentTime, ob, s)) { 85 | case (_, Nil) => sys.error("Cannot execute cross with empty bids") 86 | case (_, (o :: Nil)) => (ob.copy(bids = ob.bids - o.price), 87 | OrderExecuted(currentTime(), 88 | Execution(o.id, o.price), Execution(s.id, o.price))) 89 | case (_, (o :: qq)) => (ob.copy(bids = ob.bids + (o.price -> qq)), 90 | OrderExecuted(currentTime(), 91 | Execution(o.id, o.price), Execution(s.id, o.price))) 92 | } 93 | 94 | private def crossBookBuy( 95 | currentTime: () => EventInstant, 96 | ob: ListOrderBook, 97 | b: BuyLimitOrder): (ListOrderBook, Event) = 98 | ob.offers.headOption.fold(handleAddLimitOrder(currentTime, ob, b)) { 99 | case (_, Nil) => sys.error("Cannot execute cross with empty offers") 100 | case (_, (o :: Nil)) => (ob.copy(offers = ob.offers - o.price), 101 | OrderExecuted(currentTime(), Execution(b.id, o.price), 102 | Execution(o.id, o.price))) 103 | case (_, (o :: qq)) => (ob.copy(offers = ob.offers + (o.price -> qq)), 104 | OrderExecuted(currentTime(), 105 | Execution(b.id, o.price), Execution(o.id, o.price))) 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/QueueOrderBook.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package orderbook 3 | 4 | import scala.collection.immutable.{TreeMap, Queue} 5 | import orderbook.Commands._ 6 | import orderbook.Events._ 7 | 8 | // Taken originally from Chapter 2 9 | // http://web.archive.org/web/20110312023826/http://www.quantcup.org/home/howtohft_howtobuildafastlimitorderbook 10 | case class QueueOrderBook( 11 | bids: TreeMap[Price, Queue[BuyLimitOrder]], 12 | offers: TreeMap[Price, Queue[SellLimitOrder]]) { 13 | def bestBid: Option[BuyLimitOrder] = bids.lastOption.flatMap(_._2.headOption) 14 | def bestOffer: Option[SellLimitOrder] = 15 | offers.headOption.flatMap(_._2.headOption) 16 | } 17 | 18 | object QueueOrderBook { 19 | val noEvent: Option[Event] = None 20 | val empty: QueueOrderBook = QueueOrderBook( 21 | TreeMap.empty[Price, Queue[BuyLimitOrder]], 22 | TreeMap.empty[Price, Queue[SellLimitOrder]]) 23 | 24 | // Could make sense to handle with State monad 25 | def handle( 26 | currentTime: () => EventInstant, 27 | ob: QueueOrderBook, 28 | c: Command): (QueueOrderBook, Event) = c match { 29 | case AddLimitOrder(_, o) => handleAddLimitOrder(currentTime, ob, o) 30 | case CancelOrder(_, id) => handleCancelOrder(currentTime, ob, id) 31 | } 32 | 33 | private def handleAddLimitOrder( 34 | currentTime: () => EventInstant, 35 | ob: QueueOrderBook, 36 | o: LimitOrder): (QueueOrderBook, Event) = o match { 37 | case oo: BuyLimitOrder => 38 | ob.bestOffer.exists(oo.price.value >= _.price.value) match { 39 | case true => crossBookBuy(currentTime, ob, oo) 40 | case false => 41 | val orders = ob.bids.getOrElse(oo.price, Queue.empty) 42 | ob.copy(bids = ob.bids + (oo.price -> orders.enqueue(oo))) -> 43 | LimitOrderAdded(currentTime()) 44 | } 45 | case oo: SellLimitOrder => 46 | ob.bestBid.exists(oo.price.value <= _.price.value) match { 47 | case true => crossBookSell(currentTime, ob, oo) 48 | case false => 49 | val orders = ob.offers.getOrElse(oo.price, Queue.empty) 50 | ob.copy(offers = ob.offers + (oo.price -> orders.enqueue(oo))) -> 51 | LimitOrderAdded(currentTime()) 52 | } 53 | } 54 | 55 | private def handleCancelOrder( 56 | currentTime: () => EventInstant, 57 | ob: QueueOrderBook, 58 | id: OrderId): (QueueOrderBook, Event) = { 59 | ob.bids.find { case (p, q) => q.exists(_.id == id) }.fold( 60 | ob.offers.find { case (p, q) => q.exists(_.id == id) } 61 | .fold[(QueueOrderBook, Event)](ob -> 62 | OrderCancelRejected(currentTime(), id)) { 63 | case (p, q) => 64 | // It's awkward to duplicate the queue remove logic, but the different 65 | // types for bid vs offer make it difficult to share the code 66 | val updatedQ = q.filter(_.id != id) 67 | ob.copy(offers = updatedQ.nonEmpty match { 68 | case true => ob.offers + (p -> updatedQ) 69 | case false => ob.offers - p 70 | }) -> OrderCanceled(currentTime(), id) 71 | }) { case (p, q) => 72 | val updatedQ = q.filter(_.id != id) 73 | ob.copy(bids = updatedQ.nonEmpty match { 74 | case true => ob.bids + (p -> updatedQ) 75 | case false => ob.bids - p 76 | }) -> OrderCanceled(currentTime(), id) 77 | } 78 | } 79 | 80 | private def crossBookSell( 81 | currentTime: () => EventInstant, 82 | ob: QueueOrderBook, s: SellLimitOrder): (QueueOrderBook, Event) = 83 | ob.bids.lastOption.fold(handleAddLimitOrder(currentTime, ob, s)) { 84 | case (_, xs) => 85 | val (o, qq) = xs.dequeue 86 | (ob.copy(bids = qq.isEmpty match { 87 | case true => ob.bids - o.price 88 | case false => ob.bids + (o.price -> qq) 89 | }), OrderExecuted(currentTime(), Execution(o.id, o.price), Execution(s.id, o.price))) 90 | } 91 | 92 | private def crossBookBuy( 93 | currentTime: () => EventInstant, 94 | ob: QueueOrderBook, b: BuyLimitOrder): (QueueOrderBook, Event) = 95 | ob.offers.headOption.fold(handleAddLimitOrder(currentTime, ob, b)) { 96 | case (_, xs) => 97 | val (o, qq) = xs.dequeue 98 | (ob.copy(offers = qq.isEmpty match { 99 | case true => ob.offers - o.price 100 | case false => ob.offers + (o.price -> qq) 101 | }), OrderExecuted( 102 | currentTime(), Execution(b.id, o.price), Execution(o.id, o.price))) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/api.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | // API externalized from chapter 2 4 | 5 | object Commands { 6 | sealed trait Command 7 | case class AddLimitOrder(i: CommandInstant, o: LimitOrder) extends Command 8 | case class CancelOrder(i: CommandInstant, id: OrderId) extends Command 9 | } 10 | 11 | object Events { 12 | sealed trait Event 13 | case class OrderExecuted( 14 | i: EventInstant, buy: Execution, sell: Execution) extends Event 15 | case class LimitOrderAdded(i: EventInstant) extends Event 16 | case class OrderCancelRejected(i: EventInstant, id: OrderId) extends Event 17 | case class OrderCanceled(i: EventInstant, id: OrderId) extends Event 18 | } 19 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/model.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import org.joda.time.Instant 4 | import org.scalacheck.Gen 5 | 6 | // Model taken from chapter 2 7 | 8 | case class Price(value: BigDecimal) extends AnyVal 9 | object Price { 10 | implicit val genPrice: Gen[Price] = Gen.posNum[Double].map(d => 11 | Price(BigDecimal(d))) 12 | implicit val ordering: Ordering[Price] = new Ordering[Price] { 13 | def compare(x: Price, y: Price): Int = 14 | Ordering.BigDecimal.compare(x.value, y.value) 15 | } 16 | } 17 | 18 | case class OrderId(value: Long) 19 | object OrderId { 20 | implicit val genOrderId: Gen[OrderId] = Gen.posNum[Long].map(OrderId.apply) 21 | } 22 | 23 | sealed trait LimitOrder { 24 | def id: OrderId 25 | def price: Price 26 | } 27 | object LimitOrder { 28 | implicit val genLimitOrder: Gen[LimitOrder] = Gen.oneOf( 29 | BuyLimitOrder.genBuyLimitOrder, SellLimitOrder.genSellLimitOrder) 30 | } 31 | case class BuyLimitOrder(id: OrderId, price: Price) extends LimitOrder 32 | object BuyLimitOrder { 33 | implicit val genBuyLimitOrder: Gen[BuyLimitOrder] = Gen.zip( 34 | OrderId.genOrderId, Price.genPrice).map(Function.tupled(BuyLimitOrder.apply)) 35 | } 36 | case class SellLimitOrder(id: OrderId, price: Price) extends LimitOrder 37 | object SellLimitOrder { 38 | implicit val genSellLimitOrder: Gen[SellLimitOrder] = Gen.zip( 39 | OrderId.genOrderId, Price.genPrice).map(Function.tupled( 40 | SellLimitOrder.apply)) 41 | } 42 | 43 | case class Execution(orderId: OrderId, price: Price) 44 | 45 | case class CommandInstant(value: Instant) extends AnyVal 46 | object CommandInstant { 47 | def now(): CommandInstant = 48 | CommandInstant(new Instant(System.currentTimeMillis())) 49 | implicit val genCommandInstant: Gen[CommandInstant] = 50 | Gen.posNum[Long].map(l => CommandInstant(new Instant(l))) 51 | } 52 | 53 | case class EventInstant(value: Instant) extends AnyVal 54 | object EventInstant { 55 | def now(): EventInstant = 56 | EventInstant(new Instant(System.currentTimeMillis())) 57 | implicit val genEventInstant: Gen[EventInstant] = 58 | Gen.posNum[Long].map(l => EventInstant(new Instant(l))) 59 | } 60 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/util/DataCodec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook.util 2 | 3 | import java.io._ 4 | 5 | import highperfscala.orderbook.Commands.Command 6 | import org.slf4s.Logging 7 | 8 | import scala.collection.mutable.ListBuffer 9 | 10 | /** 11 | * This module provides functions to persist to/load from disk a list of 12 | * commands. 13 | */ 14 | object DataCodec extends Logging { 15 | 16 | def write(cs: List[Command], output: File): Unit = { 17 | val oos = new ObjectOutputStream(new FileOutputStream(output)) 18 | cs.foreach(oos.writeObject) 19 | oos.close() 20 | } 21 | 22 | def read(input: File): List[Command] = { 23 | val fis = new FileInputStream(input) 24 | val ois = new ObjectInputStream(fis) 25 | val commandBuilder = ListBuffer[Command]() 26 | while(fis.available() != 0) { 27 | commandBuilder.append(ois.readObject().asInstanceOf[Command]) 28 | } 29 | ois.close() 30 | fis.close() 31 | 32 | commandBuilder.result() 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /chapter4/src/main/scala/highperfscala/orderbook/util/package.scala: -------------------------------------------------------------------------------- 1 | package highperfscala 2 | package orderbook 3 | 4 | import Commands._ 5 | import com.codahale.metrics.Snapshot 6 | import highperfscala.orderbook.LatencyBenchmark.{LazyImplementation, QueueImplementation, BookImplementation} 7 | import org.slf4s.Logging 8 | 9 | package object util extends Logging { 10 | 11 | def infiniteCommands(sample: List[Command]): Stream[Command] = 12 | Stream.continually(sample.toStream).flatten 13 | 14 | def generateCount(sample: List[Command], count: Int): List[Command] = 15 | infiniteCommands(sample).take(count).toList 16 | 17 | def jvmWarmUp(sample: List[Command], impl: BookImplementation): Unit = { 18 | log.debug("Begin warmUp") 19 | val commands = util.generateCount(sample, 100000) 20 | val systemEventTime = () => EventInstant.now() 21 | impl match { 22 | case QueueImplementation => commands.foldLeft(QueueOrderBook.empty) { 23 | case (book, command) => QueueOrderBook.handle( 24 | systemEventTime, book, command)._1 25 | } 26 | case LazyImplementation => commands.foldLeft(LazyCancelOrderBook.empty) { 27 | case (book, command) => LazyCancelOrderBook.handle( 28 | systemEventTime, book, command)._1 29 | } 30 | } 31 | log.debug("End warmUp") 32 | } 33 | 34 | def printSnapshot(s: Snapshot): Unit = println { 35 | s""" 36 | |Processed ${s.size} commands 37 | |99p latency: ${s.get99thPercentile()} ms 38 | |99.9p latency: ${s.get999thPercentile()} ms 39 | |Maximum latency: ${s.getMax} ms 40 | """.stripMargin 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /chapter4/src/test/scala/highperfscala/dataanalysis/MidpointSeriesSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.dataanalysis 2 | 3 | import org.joda.time.DateTime 4 | import org.specs2.mutable.Specification 5 | 6 | class MidpointSeriesSpec extends Specification { 7 | 8 | "A MidpointSeries" should { 9 | 10 | "be properly created from execution data points" in { 11 | val t0 = TimestampMinutes.fromDateTime(DateTime.now) 12 | val t1 = t0.next 13 | val t2 = t1.next 14 | val t3 = t2.next 15 | val t4 = t3.next 16 | val t5 = t4.next 17 | val t6 = t5.next 18 | val executions = Vector( 19 | Execution(t0, AskPrice(40), BidPrice(20)), 20 | Execution(t1, AskPrice(30), BidPrice(25)), 21 | Execution(t1, AskPrice(50), BidPrice(22)), 22 | Execution(t4, AskPrice(24), BidPrice(16)), 23 | Execution(t4, AskPrice(84), BidPrice(78)), 24 | Execution(t4, AskPrice(64), BidPrice(37)), 25 | Execution(t6, AskPrice(41), BidPrice(23)) 26 | ) 27 | 28 | val series = MidpointSeries.fromExecution(executions) 29 | series.size ==== 7 30 | series.midpointAt(t0).get ==== Midpoint(t0, 30) 31 | series.midpointAt(t1).get ==== Midpoint(t1, 31.75) 32 | series.midpointAt(t2).get ==== Midpoint(t2, 31.75) 33 | series.midpointAt(t3).get ==== Midpoint(t3, 31.75) 34 | series.midpointAt(t4).get ==== Midpoint(t4, 50.5) 35 | series.midpointAt(t5).get ==== Midpoint(t5, 50.5) 36 | series.midpointAt(t6).get ==== Midpoint(t6, 32) 37 | series.midpointAt(t6.next) ==== None 38 | } 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /chapter4/src/test/scala/highperfscala/orderbook/LazyCancelOrderBookCancelingSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import highperfscala.orderbook.Commands.{AddLimitOrder, CancelOrder} 4 | import highperfscala.orderbook.Events.{OrderCancelRejected, OrderCanceled} 5 | import org.scalacheck.Prop 6 | import org.specs2.ScalaCheck 7 | import org.specs2.mutable.Specification 8 | 9 | class LazyCancelOrderBookCancelingSpec extends Specification with ScalaCheck { 10 | 11 | """Given empty book 12 | |When cancel order arrives 13 | |Then OrderCancelRejected 14 | """.stripMargin ! Prop.forAll( 15 | OrderId.genOrderId, 16 | CommandInstant.genCommandInstant, 17 | EventInstant.genEventInstant) { (id, ci, ei) => 18 | LazyCancelOrderBook.handle( 19 | () => ei, LazyCancelOrderBook.empty, CancelOrder(ci, id))._2 ==== 20 | OrderCancelRejected(ei, id) 21 | } 22 | 23 | """Given empty book 24 | |and buy limit order added 25 | |When cancel order arrives 26 | |Then OrderCanceled 27 | """.stripMargin ! Prop.forAll( 28 | BuyLimitOrder.genBuyLimitOrder, 29 | CommandInstant.genCommandInstant, 30 | EventInstant.genEventInstant) { (o, ci, ei) => 31 | (LazyCancelOrderBook.handle( 32 | () => ei, _: LazyCancelOrderBook, AddLimitOrder(ci, o))).andThen { 33 | case (ob, _) => LazyCancelOrderBook.handle( 34 | () => ei, ob, CancelOrder(ci, o.id))._2 35 | }(LazyCancelOrderBook.empty) ==== OrderCanceled(ei, o.id) 36 | } 37 | 38 | """Given empty book 39 | |and sell limit order added 40 | |When cancel order arrives 41 | |Then OrderCanceled 42 | """.stripMargin ! Prop.forAll( 43 | SellLimitOrder.genSellLimitOrder, 44 | CommandInstant.genCommandInstant, 45 | EventInstant.genEventInstant) { (o, ci, ei) => 46 | (LazyCancelOrderBook.handle( 47 | () => ei, _: LazyCancelOrderBook, AddLimitOrder(ci, o))).andThen { 48 | case (ob, _) => LazyCancelOrderBook.handle( 49 | () => ei, ob, CancelOrder(ci, o.id))._2 50 | }(LazyCancelOrderBook.empty) ==== OrderCanceled(ei, o.id) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /chapter4/src/test/scala/highperfscala/orderbook/LazyCancelOrderBookTradingSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import highperfscala.orderbook.Commands.{AddLimitOrder, CancelOrder} 4 | import highperfscala.orderbook.Events.{LimitOrderAdded, OrderCancelRejected, OrderExecuted} 5 | import org.scalacheck.Prop 6 | import org.specs2.ScalaCheck 7 | import org.specs2.mutable.Specification 8 | 9 | class LazyCancelOrderBookTradingSpec extends Specification with ScalaCheck { 10 | 11 | """Given empty book 12 | |When limit order arrives 13 | |Then LimitOrderAdded 14 | """.stripMargin ! Prop.forAll( 15 | LimitOrder.genLimitOrder, 16 | CommandInstant.genCommandInstant, 17 | EventInstant.genEventInstant) { (l, ci, ei) => 18 | LazyCancelOrderBook.handle( 19 | () => ei, LazyCancelOrderBook.empty, AddLimitOrder(ci, l))._2 ==== 20 | LimitOrderAdded(ei) 21 | } 22 | 23 | """Given empty book 24 | |and buy limit order added 25 | |When resting sell limit order arrives 26 | |Then LimitOrderAdded 27 | """.stripMargin ! Prop.forAll( 28 | BuyLimitOrder.genBuyLimitOrder, 29 | OrderId.genOrderId, 30 | CommandInstant.genCommandInstant, 31 | EventInstant.genEventInstant) { (buy, id, ci, ei) => 32 | (LazyCancelOrderBook.handle( 33 | () => ei, _: LazyCancelOrderBook, AddLimitOrder(ci, buy))).andThen { 34 | case (ob, _) => LazyCancelOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 35 | SellLimitOrder(id, Price(buy.price.value + 0.01))))._2 36 | }(LazyCancelOrderBook.empty) ==== LimitOrderAdded(ei) 37 | } 38 | 39 | """Given empty book 40 | |and sell limit order added 41 | |When resting buy limit order arrives 42 | |Then LimitOrderAdded 43 | """.stripMargin ! Prop.forAll( 44 | SellLimitOrder.genSellLimitOrder, 45 | OrderId.genOrderId, 46 | CommandInstant.genCommandInstant, 47 | EventInstant.genEventInstant) { (sell, id, ci, ei) => 48 | (LazyCancelOrderBook.handle(() => ei, _: LazyCancelOrderBook, AddLimitOrder(ci, sell))) 49 | .andThen { 50 | case (ob, _) => LazyCancelOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 51 | BuyLimitOrder(id, Price(sell.price.value - 0.01))))._2 52 | }(LazyCancelOrderBook.empty) ==== LimitOrderAdded(ei) 53 | } 54 | 55 | """Given empty book 56 | |and buy limit order added 57 | |When crossing sell limit order arrives 58 | |Then OrderExecuted 59 | """.stripMargin ! Prop.forAll( 60 | BuyLimitOrder.genBuyLimitOrder, 61 | OrderId.genOrderId, 62 | CommandInstant.genCommandInstant, 63 | EventInstant.genEventInstant) { (buy, id, ci, ei) => 64 | (LazyCancelOrderBook.handle(() => ei, _: LazyCancelOrderBook, AddLimitOrder(ci, buy))) 65 | .andThen { 66 | case (ob, _) => LazyCancelOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 67 | SellLimitOrder(id, Price(buy.price.value - 0.01))))._2 68 | }(LazyCancelOrderBook.empty) ==== OrderExecuted(ei, 69 | Execution(buy.id, buy.price), Execution(id, buy.price)) 70 | } 71 | 72 | """Given empty book 73 | |and sell limit order added 74 | |When crossing buy limit order arrives 75 | |Then OrderExecuted 76 | """.stripMargin ! Prop.forAll( 77 | SellLimitOrder.genSellLimitOrder, 78 | OrderId.genOrderId, 79 | CommandInstant.genCommandInstant, 80 | EventInstant.genEventInstant) { (sell, id, ci, ei) => 81 | (LazyCancelOrderBook.handle(() => ei, _: LazyCancelOrderBook, AddLimitOrder(ci, sell))) 82 | .andThen { 83 | case (ob, _) => LazyCancelOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 84 | BuyLimitOrder(id, Price(sell.price.value + 0.01))))._2 85 | }(LazyCancelOrderBook.empty) ==== OrderExecuted(ei, 86 | Execution(id, sell.price), Execution(sell.id, sell.price)) 87 | } 88 | 89 | """Given empty book 90 | |When cancel order command arrives 91 | |Then OrderCancelRejected 92 | """.stripMargin ! Prop.forAll( 93 | OrderId.genOrderId, 94 | CommandInstant.genCommandInstant, 95 | EventInstant.genEventInstant) { (id, ci, ei) => 96 | LazyCancelOrderBook.handle(() => ei, LazyCancelOrderBook.empty, CancelOrder(ci, id)) 97 | ._2 ==== OrderCancelRejected(ei, id) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /chapter4/src/test/scala/highperfscala/orderbook/ListOrderBookCancelingSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import highperfscala.orderbook.Commands.{AddLimitOrder, CancelOrder} 4 | import highperfscala.orderbook.Events.{OrderCancelRejected, OrderCanceled} 5 | import org.scalacheck.Prop 6 | import org.specs2.ScalaCheck 7 | import org.specs2.mutable.Specification 8 | 9 | class ListOrderBookCancelingSpec extends Specification with ScalaCheck { 10 | 11 | """Given empty book 12 | |When cancel order arrives 13 | |Then OrderCancelRejected 14 | """.stripMargin ! Prop.forAll( 15 | OrderId.genOrderId, 16 | CommandInstant.genCommandInstant, 17 | EventInstant.genEventInstant) { (id, ci, ei) => 18 | ListOrderBook.handle( 19 | () => ei, ListOrderBook.empty, CancelOrder(ci, id))._2 ==== 20 | OrderCancelRejected(ei, id) 21 | } 22 | 23 | """Given empty book 24 | |and buy limit order added 25 | |When cancel order arrives 26 | |Then OrderCanceled 27 | """.stripMargin ! Prop.forAll( 28 | BuyLimitOrder.genBuyLimitOrder, 29 | CommandInstant.genCommandInstant, 30 | EventInstant.genEventInstant) { (o, ci, ei) => 31 | (ListOrderBook.handle( 32 | () => ei, _: ListOrderBook, AddLimitOrder(ci, o))).andThen { 33 | case (ob, _) => ListOrderBook.handle( 34 | () => ei, ob, CancelOrder(ci, o.id))._2 35 | }(ListOrderBook.empty) ==== OrderCanceled(ei, o.id) 36 | } 37 | 38 | """Given empty book 39 | |and sell limit order added 40 | |When cancel order arrives 41 | |Then OrderCanceled 42 | """.stripMargin ! Prop.forAll( 43 | SellLimitOrder.genSellLimitOrder, 44 | CommandInstant.genCommandInstant, 45 | EventInstant.genEventInstant) { (o, ci, ei) => 46 | (ListOrderBook.handle( 47 | () => ei, _: ListOrderBook, AddLimitOrder(ci, o))).andThen { 48 | case (ob, _) => ListOrderBook.handle( 49 | () => ei, ob, CancelOrder(ci, o.id))._2 50 | }(ListOrderBook.empty) ==== OrderCanceled(ei, o.id) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /chapter4/src/test/scala/highperfscala/orderbook/ListOrderBookTradingSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import highperfscala.orderbook.Commands.{AddLimitOrder, CancelOrder} 4 | import highperfscala.orderbook.Events.{LimitOrderAdded, OrderCancelRejected, OrderExecuted} 5 | import org.scalacheck.Prop 6 | import org.specs2.ScalaCheck 7 | import org.specs2.mutable.Specification 8 | 9 | class ListOrderBookTradingSpec extends Specification with ScalaCheck { 10 | 11 | """Given empty book 12 | |When limit order arrives 13 | |Then LimitOrderAdded 14 | """.stripMargin ! Prop.forAll( 15 | LimitOrder.genLimitOrder, 16 | CommandInstant.genCommandInstant, 17 | EventInstant.genEventInstant) { (l, ci, ei) => 18 | ListOrderBook.handle( 19 | () => ei, ListOrderBook.empty, AddLimitOrder(ci, l))._2 ==== 20 | LimitOrderAdded(ei) 21 | } 22 | 23 | """Given empty book 24 | |and buy limit order added 25 | |When resting sell limit order arrives 26 | |Then LimitOrderAdded 27 | """.stripMargin ! Prop.forAll( 28 | BuyLimitOrder.genBuyLimitOrder, 29 | OrderId.genOrderId, 30 | CommandInstant.genCommandInstant, 31 | EventInstant.genEventInstant) { (buy, id, ci, ei) => 32 | (ListOrderBook.handle( 33 | () => ei, _: ListOrderBook, AddLimitOrder(ci, buy))).andThen { 34 | case (ob, _) => ListOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 35 | SellLimitOrder(id, Price(buy.price.value + 0.01))))._2 36 | }(ListOrderBook.empty) ==== LimitOrderAdded(ei) 37 | } 38 | 39 | """Given empty book 40 | |and sell limit order added 41 | |When resting buy limit order arrives 42 | |Then LimitOrderAdded 43 | """.stripMargin ! Prop.forAll( 44 | SellLimitOrder.genSellLimitOrder, 45 | OrderId.genOrderId, 46 | CommandInstant.genCommandInstant, 47 | EventInstant.genEventInstant) { (sell, id, ci, ei) => 48 | (ListOrderBook.handle(() => ei, _: ListOrderBook, AddLimitOrder(ci, sell))) 49 | .andThen { 50 | case (ob, _) => ListOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 51 | BuyLimitOrder(id, Price(sell.price.value - 0.01))))._2 52 | }(ListOrderBook.empty) ==== LimitOrderAdded(ei) 53 | } 54 | 55 | """Given empty book 56 | |and buy limit order added 57 | |When crossing sell limit order arrives 58 | |Then OrderExecuted 59 | """.stripMargin ! Prop.forAll( 60 | BuyLimitOrder.genBuyLimitOrder, 61 | OrderId.genOrderId, 62 | CommandInstant.genCommandInstant, 63 | EventInstant.genEventInstant) { (buy, id, ci, ei) => 64 | (ListOrderBook.handle(() => ei, _: ListOrderBook, AddLimitOrder(ci, buy))) 65 | .andThen { 66 | case (ob, _) => ListOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 67 | SellLimitOrder(id, Price(buy.price.value - 0.01))))._2 68 | }(ListOrderBook.empty) ==== OrderExecuted(ei, 69 | Execution(buy.id, buy.price), Execution(id, buy.price)) 70 | } 71 | 72 | """Given empty book 73 | |and sell limit order added 74 | |When crossing buy limit order arrives 75 | |Then OrderExecuted 76 | """.stripMargin ! Prop.forAll( 77 | SellLimitOrder.genSellLimitOrder, 78 | OrderId.genOrderId, 79 | CommandInstant.genCommandInstant, 80 | EventInstant.genEventInstant) { (sell, id, ci, ei) => 81 | (ListOrderBook.handle(() => ei, _: ListOrderBook, AddLimitOrder(ci, sell))) 82 | .andThen { 83 | case (ob, _) => ListOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 84 | BuyLimitOrder(id, Price(sell.price.value + 0.01))))._2 85 | }(ListOrderBook.empty) ==== OrderExecuted(ei, 86 | Execution(id, sell.price), Execution(sell.id, sell.price)) 87 | } 88 | 89 | """Given empty book 90 | |When cancel order command arrives 91 | |Then OrderCancelRejected 92 | """.stripMargin ! Prop.forAll( 93 | OrderId.genOrderId, 94 | CommandInstant.genCommandInstant, 95 | EventInstant.genEventInstant) { (id, ci, ei) => 96 | ListOrderBook.handle(() => ei, ListOrderBook.empty, CancelOrder(ci, id)) 97 | ._2 ==== OrderCancelRejected(ei, id) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /chapter4/src/test/scala/highperfscala/orderbook/QueueOrderBookCancelingSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import highperfscala.orderbook.Commands.{AddLimitOrder, CancelOrder} 4 | import highperfscala.orderbook.Events.{OrderCancelRejected, OrderCanceled} 5 | import org.scalacheck.Prop 6 | import org.specs2.ScalaCheck 7 | import org.specs2.mutable.Specification 8 | 9 | class QueueOrderBookCancelingSpec extends Specification with ScalaCheck { 10 | 11 | """Given empty book 12 | |When cancel order arrives 13 | |Then OrderCancelRejected 14 | """.stripMargin ! Prop.forAll( 15 | OrderId.genOrderId, 16 | CommandInstant.genCommandInstant, 17 | EventInstant.genEventInstant) { (id, ci, ei) => 18 | QueueOrderBook.handle( 19 | () => ei, QueueOrderBook.empty, CancelOrder(ci, id))._2 ==== 20 | OrderCancelRejected(ei, id) 21 | } 22 | 23 | """Given empty book 24 | |and buy limit order added 25 | |When cancel order arrives 26 | |Then OrderCanceled 27 | """.stripMargin ! Prop.forAll( 28 | BuyLimitOrder.genBuyLimitOrder, 29 | CommandInstant.genCommandInstant, 30 | EventInstant.genEventInstant) { (o, ci, ei) => 31 | (QueueOrderBook.handle( 32 | () => ei, _: QueueOrderBook, AddLimitOrder(ci, o))).andThen { 33 | case (ob, _) => QueueOrderBook.handle( 34 | () => ei, ob, CancelOrder(ci, o.id))._2 35 | }(QueueOrderBook.empty) ==== OrderCanceled(ei, o.id) 36 | } 37 | 38 | """Given empty book 39 | |and sell limit order added 40 | |When cancel order arrives 41 | |Then OrderCanceled 42 | """.stripMargin ! Prop.forAll( 43 | SellLimitOrder.genSellLimitOrder, 44 | CommandInstant.genCommandInstant, 45 | EventInstant.genEventInstant) { (o, ci, ei) => 46 | (QueueOrderBook.handle( 47 | () => ei, _: QueueOrderBook, AddLimitOrder(ci, o))).andThen { 48 | case (ob, _) => QueueOrderBook.handle( 49 | () => ei, ob, CancelOrder(ci, o.id))._2 50 | }(QueueOrderBook.empty) ==== OrderCanceled(ei, o.id) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /chapter4/src/test/scala/highperfscala/orderbook/QueueOrderBookTradingSpec.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.orderbook 2 | 3 | import highperfscala.orderbook.Commands.{AddLimitOrder, CancelOrder} 4 | import highperfscala.orderbook.Events.{LimitOrderAdded, OrderCancelRejected, OrderExecuted} 5 | import org.scalacheck.Prop 6 | import org.specs2.ScalaCheck 7 | import org.specs2.mutable.Specification 8 | 9 | class QueueOrderBookTradingSpec extends Specification with ScalaCheck { 10 | 11 | """Given empty book 12 | |When limit order arrives 13 | |Then LimitOrderAdded 14 | """.stripMargin ! Prop.forAll( 15 | LimitOrder.genLimitOrder, 16 | CommandInstant.genCommandInstant, 17 | EventInstant.genEventInstant) { (l, ci, ei) => 18 | QueueOrderBook.handle( 19 | () => ei, QueueOrderBook.empty, AddLimitOrder(ci, l))._2 ==== 20 | LimitOrderAdded(ei) 21 | } 22 | 23 | """Given empty book 24 | |and buy limit order added 25 | |When resting sell limit order arrives 26 | |Then LimitOrderAdded 27 | """.stripMargin ! Prop.forAll( 28 | BuyLimitOrder.genBuyLimitOrder, 29 | OrderId.genOrderId, 30 | CommandInstant.genCommandInstant, 31 | EventInstant.genEventInstant) { (buy, id, ci, ei) => 32 | ( QueueOrderBook.handle( 33 | () => ei, _: QueueOrderBook, AddLimitOrder(ci, buy))).andThen { 34 | case (ob, _) => QueueOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 35 | SellLimitOrder(id, Price(buy.price.value + 0.01))))._2 36 | }( QueueOrderBook.empty) ==== LimitOrderAdded(ei) 37 | } 38 | 39 | """Given empty book 40 | |and sell limit order added 41 | |When resting buy limit order arrives 42 | |Then LimitOrderAdded 43 | """.stripMargin ! Prop.forAll( 44 | SellLimitOrder.genSellLimitOrder, 45 | OrderId.genOrderId, 46 | CommandInstant.genCommandInstant, 47 | EventInstant.genEventInstant) { (sell, id, ci, ei) => 48 | ( QueueOrderBook.handle(() => ei, _: QueueOrderBook, AddLimitOrder(ci, sell))) 49 | .andThen { 50 | case (ob, _) => QueueOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 51 | BuyLimitOrder(id, Price(sell.price.value - 0.01))))._2 52 | }( QueueOrderBook.empty) ==== LimitOrderAdded(ei) 53 | } 54 | 55 | """Given empty book 56 | |and buy limit order added 57 | |When crossing sell limit order arrives 58 | |Then OrderExecuted 59 | """.stripMargin ! Prop.forAll( 60 | BuyLimitOrder.genBuyLimitOrder, 61 | OrderId.genOrderId, 62 | CommandInstant.genCommandInstant, 63 | EventInstant.genEventInstant) { (buy, id, ci, ei) => 64 | ( QueueOrderBook.handle(() => ei, _: QueueOrderBook, AddLimitOrder(ci, buy))) 65 | .andThen { 66 | case (ob, _) => QueueOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 67 | SellLimitOrder(id, Price(buy.price.value - 0.01))))._2 68 | }( QueueOrderBook.empty) ==== OrderExecuted(ei, 69 | Execution(buy.id, buy.price), Execution(id, buy.price)) 70 | } 71 | 72 | """Given empty book 73 | |and sell limit order added 74 | |When crossing buy limit order arrives 75 | |Then OrderExecuted 76 | """.stripMargin ! Prop.forAll( 77 | SellLimitOrder.genSellLimitOrder, 78 | OrderId.genOrderId, 79 | CommandInstant.genCommandInstant, 80 | EventInstant.genEventInstant) { (sell, id, ci, ei) => 81 | ( QueueOrderBook.handle(() => ei, _: QueueOrderBook, AddLimitOrder(ci, sell))) 82 | .andThen { 83 | case (ob, _) => QueueOrderBook.handle(() => ei, ob, AddLimitOrder(ci, 84 | BuyLimitOrder(id, Price(sell.price.value + 0.01))))._2 85 | }( QueueOrderBook.empty) ==== OrderExecuted(ei, 86 | Execution(id, sell.price), Execution(sell.id, sell.price)) 87 | } 88 | 89 | """Given empty book 90 | |When cancel order command arrives 91 | |Then OrderCancelRejected 92 | """.stripMargin ! Prop.forAll( 93 | OrderId.genOrderId, 94 | CommandInstant.genCommandInstant, 95 | EventInstant.genEventInstant) { (id, ci, ei) => 96 | QueueOrderBook.handle(() => ei, QueueOrderBook.empty, CancelOrder(ci, id)) 97 | ._2 ==== OrderCancelRejected(ei, id) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /chapter4/target/.history: -------------------------------------------------------------------------------- 1 | console 2 | run test.txt 300 3 | jmh:run ReturnsBenchmarks 4 | console 5 | jmh:run ReturnsBenchmarks 6 | jmh:run ListVectorBenchmarks 7 | jmh:run ReturnSeriesFrameBenchmarks 8 | -------------------------------------------------------------------------------- /chapter4/target/streams/$global/clean/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter4/target/streams/$global/clean/$global/streams/out -------------------------------------------------------------------------------- /chapter4/target/streams/$global/ivyConfiguration/$global/streams/out: -------------------------------------------------------------------------------- 1 | [debug] Other repositories: 2 | [debug] Default repositories: 3 | [debug] Using inline dependencies specified in Scala. 4 | -------------------------------------------------------------------------------- /chapter4/target/streams/$global/ivySbt/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter4/target/streams/$global/ivySbt/$global/streams/out -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/streams/EventSourcing.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.streams 2 | 3 | import org.joda.time.{Duration, Instant} 4 | 5 | object EventSourcing { 6 | 7 | sealed trait PnlEvent 8 | case class PnlIncreased( 9 | created: EventInstant, clientId: ClientId, ticker: Ticker, profit: Pnl 10 | ) extends PnlEvent 11 | case class PnlDecreased( 12 | created: EventInstant, clientId: ClientId, ticker: Ticker, loss: Pnl) 13 | extends PnlEvent 14 | 15 | case class Pnl(value: BigDecimal) extends AnyVal { 16 | def isProfit: Boolean = value.signum >= 0 17 | def +(p: Pnl): Pnl = Pnl(value + p.value) 18 | def -(p: Pnl): Pnl = Pnl(value - p.value) 19 | } 20 | object Pnl { 21 | def fromBidExecution(bid: Price, buy: Price): Pnl = 22 | Pnl(bid.value - buy.value) 23 | def fromOfferExecution(offer: Price, sell: Price): Pnl = 24 | Pnl(sell.value - offer.value) 25 | val zero: Pnl = Pnl(BigDecimal(0)) 26 | } 27 | 28 | case class PendingOrder(ticker: Ticker, price: Price, clientId: ClientId) 29 | case class TradeState( 30 | pendingBuys: Map[OrderId, PendingOrder], 31 | pendingSells: Map[OrderId, PendingOrder]) { 32 | def cancelOrder(id: OrderId): TradeState = copy( 33 | pendingBuys = pendingBuys - id, pendingSells = pendingSells - id) 34 | def addPendingBuy(o: PendingOrder, id: OrderId): TradeState = 35 | copy(pendingBuys = pendingBuys + (id -> o)) 36 | def addPendingSell(o: PendingOrder, id: OrderId): TradeState = 37 | copy(pendingSells = pendingSells + (id -> o)) 38 | } 39 | object TradeState { 40 | val empty: TradeState = TradeState(Map.empty, Map.empty) 41 | } 42 | 43 | def processPnl( 44 | s: TradeState, 45 | e: OrderBookEvent): (TradeState, Option[PnlEvent]) = e match { 46 | case BuyOrderSubmitted(_, id, t, p, cId) => 47 | s.addPendingBuy(PendingOrder(t, p, cId), id) -> None 48 | case SellOrderSubmitted(_, id, t, p, cId) => 49 | s.addPendingSell(PendingOrder(t, p, cId), id) -> None 50 | case OrderCanceled(_, id) => s.cancelOrder(id) -> None 51 | case OrderExecuted(ts, id, price) => 52 | val (p, o) = (s.pendingBuys.get(id), s.pendingSells.get(id)) match { 53 | case (Some(order), None) => 54 | Pnl.fromBidExecution(order.price, price) -> order 55 | case (None, Some(order)) => 56 | Pnl.fromOfferExecution(price, order.price) -> order 57 | case error => sys.error( 58 | s"Unsupported retrieval of ID = $id returned: $error") 59 | } 60 | s.cancelOrder(id) -> Some( 61 | if (p.isProfit) PnlIncreased(ts, o.clientId, o.ticker, p) 62 | else PnlDecreased(ts, o.clientId, o.ticker, p)) 63 | } 64 | 65 | sealed trait LastHourPnL 66 | case object LastHourPositive extends LastHourPnL 67 | case object LastHourNegative extends LastHourPnL 68 | 69 | case class HourInstant(value: Instant) extends AnyVal { 70 | def isSameHour(h: HourInstant): Boolean = 71 | h.value.toDateTime.getHourOfDay == value.toDateTime.getHourOfDay 72 | } 73 | object HourInstant { 74 | def create(i: EventInstant): HourInstant = 75 | HourInstant(i.value.toDateTime.withMillisOfSecond(0) 76 | .withSecondOfMinute(0).withMinuteOfHour(0).toInstant) 77 | } 78 | case class HourlyPnlTrendCalculated( 79 | start: HourInstant, 80 | clientId: ClientId, 81 | ticker: Ticker, 82 | pnl: LastHourPnL) 83 | 84 | case class HourlyState( 85 | keyToHourlyPnl: Map[(ClientId, Ticker), (HourInstant, Pnl)]) 86 | object HourlyState { 87 | val empty: HourlyState = HourlyState(Map.empty) 88 | } 89 | def processHourlyPnl( 90 | s: HourlyState, 91 | e: PnlEvent): (HourlyState, Option[HourlyPnlTrendCalculated]) = { 92 | def processChange( 93 | ts: EventInstant, 94 | clientId: ClientId, 95 | ticker: Ticker, 96 | pnl: Pnl): (HourlyState, Option[HourlyPnlTrendCalculated]) = { 97 | val (start, p) = s.keyToHourlyPnl.get((clientId, ticker)).fold( 98 | (HourInstant.create(ts), Pnl.zero))(identity) 99 | start.isSameHour(HourInstant.create(ts)) match { 100 | case true => (s.copy(keyToHourlyPnl = s.keyToHourlyPnl + 101 | ((clientId, ticker) ->(start, p + pnl))), None) 102 | case false => (s.copy(keyToHourlyPnl = 103 | s.keyToHourlyPnl + ((clientId, ticker) -> 104 | (HourInstant.create(ts), Pnl.zero + pnl))), 105 | Some(HourlyPnlTrendCalculated(start, clientId, ticker, 106 | p.isProfit match { 107 | case true => LastHourPositive 108 | case false => LastHourNegative 109 | }))) 110 | } 111 | } 112 | 113 | e match { 114 | case PnlIncreased(ts, clientId, ticker, pnl) => processChange( 115 | ts, clientId, ticker, pnl) 116 | case PnlDecreased(ts, clientId, ticker, pnl) => processChange( 117 | ts, clientId, ticker, pnl) 118 | } 119 | } 120 | 121 | case class PipelineState(tradeState: TradeState, hourlyState: HourlyState) 122 | object PipelineState { 123 | val empty: PipelineState = PipelineState(TradeState.empty, HourlyState.empty) 124 | } 125 | 126 | def pipeline( 127 | initial: PipelineState, 128 | f: HourlyPnlTrendCalculated => Unit, 129 | xs: Stream[OrderBookEvent]): PipelineState = xs.foldLeft(initial) { 130 | case (PipelineState(ts, hs), e) => 131 | val (tss, pnlEvent) = processPnl(ts, e) 132 | PipelineState(tss, 133 | pnlEvent.map(processHourlyPnl(hs, _)).fold(hs) { 134 | case (hss, Some(hourlyEvent)) => 135 | f(hourlyEvent) 136 | hss 137 | case (hss, None) => hss 138 | }) 139 | } 140 | 141 | def main(args: Array[String]): Unit = { 142 | val now = EventInstant(HourInstant.create(EventInstant( 143 | new Instant())).value) 144 | val Foo = Ticker("FOO") 145 | 146 | pipeline(PipelineState.empty, println, Stream( 147 | BuyOrderSubmitted(now, OrderId(1), Foo, Price(21.07), ClientId(1)), 148 | OrderExecuted(EventInstant(now.value.plus(Duration.standardMinutes(30))), 149 | OrderId(1), Price(21.00)), 150 | BuyOrderSubmitted(EventInstant(now.value.plus( 151 | Duration.standardMinutes(35))), 152 | OrderId(2), Foo, Price(24.02), ClientId(1)), 153 | OrderExecuted(EventInstant(now.value.plus(Duration.standardHours(1))), 154 | OrderId(2), Price(24.02)))) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/streams/MarkovChainEventGenerator.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.streams 2 | 3 | import org.joda.time.Instant 4 | 5 | import scala.annotation.tailrec 6 | import scala.util.Random 7 | 8 | object MarkovChainEventGenerator { 9 | 10 | sealed trait Step 11 | case object GenerateBuy extends Step 12 | case object GenerateSell extends Step 13 | case object GenerateCancel extends Step 14 | case object GenerateExecution extends Step 15 | 16 | case class Weight(value: Int) extends AnyVal 17 | 18 | case class GeneratedWeight(value: Int) extends AnyVal 19 | 20 | case class StepTransitionWeights( 21 | buy: Weight, 22 | sell: Weight, 23 | cancel: Weight, 24 | execution: Weight) { 25 | 26 | def weightSum: Weight = 27 | Weight(buy.value + sell.value + cancel.value + execution.value) 28 | 29 | private val buyRange = 1 to buy.value 30 | 31 | private val sellRange = { 32 | val start = buy.value + 1 33 | start to start + sell.value - 1 34 | } 35 | private val cancelRange = { 36 | val start = buy.value + sell.value + 1 37 | start to start + cancel.value - 1 38 | } 39 | 40 | private val executionRange = { 41 | val start = buy.value + sell.value + cancel.value + 1 42 | start to start + execution.value - 1 43 | } 44 | 45 | private val allRanges = 46 | List(buyRange, sellRange, cancelRange, executionRange) 47 | 48 | def stepFrom(gw: GeneratedWeight): Step = 49 | allRanges.map(_.contains(gw.value)) match { 50 | case true :: false :: false :: false :: Nil => GenerateBuy 51 | case false :: true :: false :: false :: Nil => GenerateSell 52 | case false :: false :: true :: false :: Nil => GenerateCancel 53 | case false :: false :: false :: true :: Nil => GenerateExecution 54 | case unsupported => sys.error( 55 | s"Unsupported result for weight = $gw: $unsupported") 56 | } 57 | } 58 | 59 | case class State( 60 | pendingOrders: Set[OrderId], 61 | step: Step) 62 | object State { 63 | private val clientIds: Vector[ClientId] = 64 | (1 to 100).map(i => ClientId(i)).toVector 65 | 66 | private val tickers: Vector[Ticker] = Vector( 67 | "FOO", "BAR", "XYZ", "RTY", "PLM").map(Ticker.apply) 68 | 69 | val initialBuy: (State, OrderBookEvent) = { 70 | val e = randomBuySubmitted() 71 | State(Set(e.id), GenerateBuy) -> e 72 | } 73 | 74 | val initialSell: (State, OrderBookEvent) = { 75 | val e = randomSellSubmitted() 76 | State(Set(e.id), GenerateSell) -> e 77 | } 78 | 79 | private def randomBuySubmitted() = BuyOrderSubmitted( 80 | EventInstant(Instant.now()), OrderId(Math.abs(Random.nextLong())), 81 | tickers(Random.nextInt(tickers.size)), Price(Random.nextInt(100) + 1), 82 | clientIds(Random.nextInt(clientIds.size))) 83 | 84 | private def randomSellSubmitted() = SellOrderSubmitted( 85 | EventInstant(Instant.now()), OrderId(Math.abs(Random.nextLong())), 86 | tickers(Random.nextInt(tickers.size)), Price(Random.nextInt(100) + 1), 87 | clientIds(Random.nextInt(clientIds.size))) 88 | 89 | private def randomCanceled(id: OrderId) = OrderCanceled( 90 | EventInstant(Instant.now()), id) 91 | 92 | private def randomExecuted(id: OrderId) = OrderExecuted( 93 | EventInstant(Instant.now()), id, Price(Random.nextInt(100) + 1)) 94 | 95 | def nextState( 96 | weight: StepTransitionWeights => GeneratedWeight, 97 | stepToWeights: Map[Step, StepTransitionWeights], 98 | s: State): (State, OrderBookEvent) = { 99 | @tailrec 100 | def loop(): (State, OrderBookEvent) = { 101 | val transition = stepToWeights(s.step) 102 | val nextStep = transition.stepFrom(weight(transition)) 103 | nextStep match { 104 | case GenerateBuy => 105 | val e = randomBuySubmitted() 106 | s.copy(step = nextStep, pendingOrders = s.pendingOrders + e.id) -> e 107 | case GenerateSell => 108 | val e = randomSellSubmitted() 109 | s.copy(step = nextStep, pendingOrders = s.pendingOrders + e.id) -> e 110 | case GenerateCancel if s.pendingOrders.nonEmpty => 111 | val e = randomCanceled(s.pendingOrders.head) 112 | s.copy(step = nextStep, pendingOrders = s.pendingOrders - e.id) -> e 113 | case GenerateCancel if s.pendingOrders.isEmpty => loop() 114 | case GenerateExecution if s.pendingOrders.nonEmpty => 115 | val e = randomExecuted(s.pendingOrders.head) 116 | s.copy(step = nextStep, pendingOrders = s.pendingOrders - e.id) -> e 117 | case GenerateExecution if s.pendingOrders.isEmpty => loop() 118 | } 119 | } 120 | loop() 121 | } 122 | } 123 | 124 | def main(args: Array[String]): Unit = { 125 | val stepToWeights = Map[Step, StepTransitionWeights]( 126 | GenerateBuy -> StepTransitionWeights( 127 | Weight(10), Weight(25), Weight(40), Weight(40)), 128 | GenerateSell -> StepTransitionWeights( 129 | Weight(25), Weight(10), Weight(40), Weight(25)), 130 | GenerateCancel -> StepTransitionWeights( 131 | Weight(60), Weight(50), Weight(40), Weight(10)), 132 | GenerateExecution -> StepTransitionWeights( 133 | Weight(30), Weight(30), Weight(60), Weight(25))) 134 | 135 | val next = State.nextState( 136 | t => GeneratedWeight(Random.nextInt(t.weightSum.value) + 1), 137 | stepToWeights, _: State) 138 | 139 | Stream.iterate(State.initialBuy) { case (s, e) => next(s) } 140 | .take(5) 141 | .foreach { case (s, e) => println(s"State = $s\nEvent = $e") } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/streams/StreamsExamples.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.streams 2 | 3 | import scala.annotation.tailrec 4 | 5 | object StreamsExamples { 6 | 7 | def powerOf2: Stream[Int] = { 8 | def next(n: Int): Stream[Int] = { 9 | println(s"Adding $n") 10 | n #:: next(2 * n) 11 | } 12 | 1 #:: next(1) 13 | } 14 | 15 | def recursivePowerOf2(n: Int): Stream[Int] = 16 | math.pow(2, n).toInt #:: recursivePowerOf2(n+1) 17 | 18 | @tailrec 19 | def drop[A](s: Stream[A], count: Int): Stream[A] = count match { 20 | case 0 => s 21 | case n if n > 0 => drop(s.tail, count - 1) 22 | case n if n < 0 => throw new Exception("cannot drop negative count") 23 | } 24 | 25 | def max(s: => Stream[Int]): Option[Int] = { 26 | @tailrec 27 | def loop(ss: Stream[Int], current: Option[Int]): Option[Int] = ss match { 28 | case Stream.Empty => current 29 | case h #:: rest if current.exists(_ >= h) => loop(rest, current) 30 | case h #:: rest => loop(rest, Some(h)) 31 | } 32 | loop(s, None) 33 | } 34 | 35 | def memoizingMax(s: => Stream[Int]): Option[Int] = { 36 | @tailrec 37 | def loop(ss: Stream[Int], current: Int): Int = ss match { 38 | case Stream.Empty => current 39 | case h #:: rest if h > current => loop(rest, h) 40 | case h #:: rest if h <= current => loop(rest, current) 41 | } 42 | 43 | // This pattern matching creates a reference to the tail of s and 44 | // prevents eager garbage collection of the intermediate elements. 45 | s match { 46 | case Stream.Empty => None 47 | case h #:: rest => Some(loop(rest, h)) 48 | } 49 | } 50 | 51 | def range(s: Seq[Int]): Int = s.max - s.min 52 | 53 | } 54 | -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/streams/model.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.streams 2 | 3 | import org.joda.time.Instant 4 | 5 | case class Ticker(value: String) extends AnyVal 6 | case class Price(value: BigDecimal) extends AnyVal 7 | case class OrderId(value: Long) extends AnyVal 8 | case class EventInstant(value: Instant) extends AnyVal 9 | case class ClientId(value: Long) extends AnyVal 10 | 11 | sealed trait Order { 12 | def created: EventInstant 13 | def id: OrderId 14 | def ticker: Ticker 15 | def price: Price 16 | def clientId: ClientId 17 | } 18 | case class BuyOrder( 19 | created: EventInstant, id: OrderId, ticker: Ticker, price: Price, 20 | clientId: ClientId) extends Order 21 | case class SellOrder( 22 | created: EventInstant, id: OrderId, ticker: Ticker, price: Price, 23 | clientId: ClientId) extends Order 24 | 25 | case class Execution(created: EventInstant, id: OrderId, price: Price) 26 | 27 | 28 | sealed trait OrderBookEvent 29 | case class BuyOrderSubmitted( 30 | created: EventInstant, id: OrderId, ticker: Ticker, price: Price, 31 | clientId: ClientId) extends OrderBookEvent 32 | case class SellOrderSubmitted( 33 | created: EventInstant, id: OrderId, ticker: Ticker, price: Price, 34 | clientId: ClientId) extends OrderBookEvent 35 | case class OrderCanceled(created: EventInstant, id: OrderId) 36 | extends OrderBookEvent 37 | case class OrderExecuted(created: EventInstant, id: OrderId, price: Price) 38 | extends OrderBookEvent -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/views/PerformanceReporting.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.views 2 | 3 | import org.joda.time.{Duration, Instant, Interval} 4 | 5 | object PerformanceReporting { 6 | 7 | def trend( 8 | now: () => Instant, 9 | findOrders: (Interval, Ticker) => List[Order], 10 | findExecutions: (Interval, Ticker) => List[Execution], 11 | request: GenerateTradingPerformanceTrend): List[TradingPerformanceTrend] = { 12 | def periodPnL( 13 | duration: Duration): Map[Ticker, PeriodPnL] = { 14 | val currentTime = now() 15 | val interval = new Interval(currentTime.minus(duration), currentTime) 16 | (for { 17 | ticker <- request.tickers 18 | orders = findOrders(interval, ticker) 19 | executions = findExecutions(interval, ticker) 20 | idToExecPrice = executions.groupBy(_.id).mapValues(es => 21 | Price.average(es.map(_.price))) 22 | signedExecutionPrices = for { 23 | o <- orders 24 | if o.clientId == request.clientId 25 | price <- idToExecPrice.get(o.id).map(p => o match { 26 | case _: BuyOrder => Price(p.value * -1) 27 | case _: SellOrder => p 28 | }).toList 29 | } yield price 30 | trend = signedExecutionPrices.foldLeft(PnL.zero) { 31 | case (pnl, p) => PnL(pnl.value + p.value) 32 | } match { 33 | case p if p.value >= PnL.zero.value => PeriodPositive 34 | case _ => PeriodNegative 35 | } 36 | } yield ticker -> trend).toMap 37 | } 38 | 39 | val tickerToLastHour = periodPnL(Duration.standardHours(1)).mapValues { 40 | case PeriodPositive => LastHourPositive 41 | case PeriodNegative => LastHourNegative 42 | } 43 | val tickerToLastDay = periodPnL(Duration.standardDays(1)).mapValues { 44 | case PeriodPositive => LastDayPositive 45 | case PeriodNegative => LastDayNegative 46 | } 47 | val tickerToLastSevenDays = periodPnL(Duration.standardDays(7)).mapValues { 48 | case PeriodPositive => LastSevenDayPositive 49 | case PeriodNegative => LastSevenDayNegative 50 | } 51 | 52 | tickerToLastHour.zip(tickerToLastDay).zip(tickerToLastSevenDays).map({ 53 | case (((t, lastHour), (_, lastDay)), (_, lastSevenDays)) => 54 | TradingPerformanceTrend(t, lastHour, lastDay, lastSevenDays) 55 | }).toList 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/views/ViewBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.views 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations.Mode.Throughput 6 | import org.openjdk.jmh.annotations._ 7 | 8 | @BenchmarkMode(Array(Throughput)) 9 | @OutputTimeUnit(TimeUnit.SECONDS) 10 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 11 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 12 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 13 | class ViewBenchmarks { 14 | 15 | import ViewBenchmarks._ 16 | 17 | @Benchmark 18 | def singleTransformList(state: ViewState): List[Int] = 19 | state.numbers.map(_ * 2) 20 | 21 | @Benchmark 22 | def singleTransformView(state: ViewState): Vector[Int] = 23 | state.numbers.view.map(_ * 2).toVector 24 | 25 | @Benchmark 26 | def twoTransformsList(state: ViewState): List[Int] = 27 | state.numbers.map(_ * 2).filter(_ % 3 == 0) 28 | 29 | @Benchmark 30 | def twoTransformsView(state: ViewState): Vector[Int] = 31 | state.numbers.view.map(_ * 2).filter(_ % 3 == 0).toVector 32 | 33 | @Benchmark 34 | def threeTransformsList(state: ViewState): List[Int] = 35 | state.numbers.map(_ * 2).map(_ + 7).filter(_ % 3 == 0) 36 | 37 | @Benchmark 38 | def threeTransformsView(state: ViewState): Vector[Int] = 39 | state.numbers.view.map(_ * 2).map(_ + 7).filter(_ % 3 == 0).toVector 40 | } 41 | 42 | object ViewBenchmarks { 43 | 44 | @State(Scope.Benchmark) 45 | class ViewState { 46 | 47 | @Param(Array("10", "1000", "1000000")) 48 | var collectionSize: Int = 0 49 | 50 | var numbers: List[Int] = Nil 51 | 52 | @Setup 53 | def setup(): Unit = { 54 | numbers = (for (i <- 1 to collectionSize) yield i).toList 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/views/ViewDemo.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.views 2 | 3 | object ViewDemo { 4 | 5 | def main(args: Array[String]): Unit = { 6 | println("List evaluation:") 7 | val evens = List(0, 1, 2, 3, 4, 5).map(i => { 8 | println(s"Adding one to $i") 9 | i + 1 10 | }).filter(i => { 11 | println(s"Filtering $i") 12 | i % 2 == 0 13 | }) 14 | 15 | println("--- Printing first two even elements ---") 16 | println(evens.take(2)) 17 | 18 | println("View evaluation:") 19 | val evensView = List(0, 1, 2, 3, 4, 5).view.map(i => { 20 | println(s"Adding one to $i") 21 | i + 1 22 | }).filter(i => { 23 | println(s"Filtering $i") 24 | i % 2 == 0 25 | }) 26 | 27 | println("--- Printing first two even elements ---") 28 | println(evensView.take(2).toList) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/views/ViewPerformanceReporting.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.views 2 | 3 | import org.joda.time.{Duration, Instant, Interval} 4 | 5 | object ViewPerformanceReporting { 6 | 7 | def trend( 8 | now: () => Instant, 9 | findOrders: (Interval, Ticker) => List[Order], 10 | findExecutions: (Interval, Ticker) => List[Execution], 11 | request: GenerateTradingPerformanceTrend): List[TradingPerformanceTrend] = { 12 | 13 | def periodPnL( 14 | duration: Duration): Map[Ticker, PeriodPnL] = { 15 | val currentTime = now() 16 | val interval = new Interval(currentTime.minus(duration), currentTime) 17 | (for { 18 | ticker <- request.tickers 19 | orders = findOrders(interval, ticker) 20 | executions = findExecutions(interval, ticker) 21 | idToExecPrice = executions.groupBy(_.id).mapValues(es => 22 | Price.average(es.map(_.price))) 23 | signedExecutionPrices = for { 24 | o <- orders.view 25 | if o.clientId == request.clientId 26 | price <- idToExecPrice.get(o.id).map(p => o match { 27 | case _: BuyOrder => Price(p.value * -1) 28 | case _: SellOrder => p 29 | }).toList 30 | } yield price 31 | trend = signedExecutionPrices.foldLeft(PnL.zero) { 32 | case (pnl, p) => PnL(pnl.value + p.value) 33 | } match { 34 | case p if p.value >= PnL.zero.value => PeriodPositive 35 | case _ => PeriodNegative 36 | } 37 | } yield ticker -> trend).toMap 38 | } 39 | 40 | val tickerToLastHour = periodPnL(Duration.standardHours(1)).mapValues { 41 | case PeriodPositive => LastHourPositive 42 | case PeriodNegative => LastHourNegative 43 | } 44 | val tickerToLastDay = periodPnL(Duration.standardDays(1)).mapValues { 45 | case PeriodPositive => LastDayPositive 46 | case PeriodNegative => LastDayNegative 47 | } 48 | val tickerToLastSevenDays = periodPnL(Duration.standardDays(7)).mapValues { 49 | case PeriodPositive => LastSevenDayPositive 50 | case PeriodNegative => LastSevenDayNegative 51 | } 52 | 53 | tickerToLastHour.zip(tickerToLastDay).zip(tickerToLastSevenDays).map({ 54 | case (((t, lastHour), (_, lastDay)), (_, lastSevenDays)) => 55 | TradingPerformanceTrend(t, lastHour, lastDay, lastSevenDays) 56 | }).toList 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/views/api.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.views 2 | 3 | import org.joda.time.Instant 4 | 5 | sealed trait LastHourPnL 6 | case object LastHourPositive extends LastHourPnL 7 | case object LastHourNegative extends LastHourPnL 8 | 9 | sealed trait LastDayPnL 10 | case object LastDayPositive extends LastDayPnL 11 | case object LastDayNegative extends LastDayPnL 12 | 13 | sealed trait LastSevenDayPnL 14 | case object LastSevenDayPositive extends LastSevenDayPnL 15 | case object LastSevenDayNegative extends LastSevenDayPnL 16 | 17 | case class Ticker(value: String) extends AnyVal 18 | 19 | case class TradingPerformanceTrend( 20 | ticker: Ticker, 21 | lastHour: LastHourPnL, 22 | lastDay: LastDayPnL, 23 | lastSevenDay: LastSevenDayPnL) 24 | 25 | case class Price(value: BigDecimal) extends AnyVal 26 | object Price { 27 | def average(ps: List[Price]): Price = { 28 | val prices = ps.map(_.value) 29 | Price(prices.sum / prices.length) 30 | } 31 | } 32 | case class OrderId(value: Long) extends AnyVal 33 | case class CreatedTimestamp(value: Instant) extends AnyVal 34 | case class ClientId(value: Long) extends AnyVal 35 | sealed trait Order { 36 | def created: CreatedTimestamp 37 | def id: OrderId 38 | def ticker: Ticker 39 | def price: Price 40 | def clientId: ClientId 41 | } 42 | case class BuyOrder( 43 | created: CreatedTimestamp, id: OrderId, ticker: Ticker, price: Price, 44 | clientId: ClientId) extends Order 45 | case class SellOrder( 46 | created: CreatedTimestamp, id: OrderId, ticker: Ticker, price: Price, 47 | clientId: ClientId) extends Order 48 | 49 | case class Execution(created: CreatedTimestamp, id: OrderId, price: Price) 50 | 51 | case class PnL(value: BigDecimal) extends AnyVal 52 | object PnL { 53 | val zero: PnL = PnL(BigDecimal(0)) 54 | } 55 | 56 | sealed trait PeriodPnL 57 | case object PeriodPositive extends PeriodPnL 58 | case object PeriodNegative extends PeriodPnL 59 | 60 | case class GenerateTradingPerformanceTrend( 61 | tickers: List[Ticker], clientId: ClientId) 62 | -------------------------------------------------------------------------------- /chapter5/src/main/scala/highperfscala/clientreports/views/pseudoView.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.clientreports.views 2 | 3 | sealed trait PseudoView[A] { 4 | def map[B](f: A => B): PseudoView[B] 5 | def toList: List[A] 6 | } 7 | 8 | final class InitialView[A](xs: List[A]) extends PseudoView[A] { 9 | def map[B](f: A => B): PseudoView[B] = new ComposedView[A, B](xs, f) 10 | def toList: List[A] = xs 11 | } 12 | 13 | final class ComposedView[A, B](xs: List[A], fa: A => B) extends PseudoView[B] { 14 | def map[C](f: B => C): PseudoView[C] = new ComposedView(xs, f.compose(fa)) 15 | def toList: List[B] = xs.map(fa) 16 | } 17 | 18 | object PseudoView { 19 | def view[A, B](xs: List[A]): PseudoView[A] = new InitialView(xs) 20 | } 21 | 22 | object PseudoViewExample { 23 | 24 | def main(args: Array[String]): Unit = { 25 | println("PseudoView evaluation:") 26 | val listPseudoView = PseudoView.view(List(0, 1, 2)).map(i => { 27 | println(s"Adding one to $i") 28 | i + 1 29 | }).map(i => { 30 | println(s"Multiplying $i") 31 | i * 2 32 | }) 33 | 34 | println("--- Converting PseudoView to List ---") 35 | println(listPseudoView.toList) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter5/target/.history: -------------------------------------------------------------------------------- 1 | jmh:run ViewBenchmarks -wi 3 -w 5s -i 30 -r 10s -f 1 -jvmArgs "-Xmx1G -Xms1G" -foe true 2 | jmh:run ViewBenchmarks 3 | -------------------------------------------------------------------------------- /chapter5/target/streams/$global/clean/$global/streams/out: -------------------------------------------------------------------------------- 1 | [debug] no default cache defined: set to /Users/vincent/.ivy2/cache 2 | -------------------------------------------------------------------------------- /chapter5/target/streams/$global/ivyConfiguration/$global/streams/out: -------------------------------------------------------------------------------- 1 | [debug] Other repositories: 2 | [debug] Default repositories: 3 | [debug] Using inline dependencies specified in Scala. 4 | -------------------------------------------------------------------------------- /chapter5/target/streams/$global/ivySbt/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter5/target/streams/$global/ivySbt/$global/streams/out -------------------------------------------------------------------------------- /chapter5/target/streams/$global/projectDescriptors/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter5/target/streams/$global/projectDescriptors/$global/streams/out -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/backtesting/Backtest.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.backtesting 2 | 3 | import org.joda.time.MonthDay 4 | 5 | import scalaz.concurrent.Task 6 | 7 | object Backtest { 8 | 9 | case class PnL(value: BigDecimal) extends AnyVal 10 | object PnL { 11 | def merge(x: PnL, y: PnL): PnL = PnL(x.value + y.value) 12 | val zero: PnL = PnL(0) 13 | } 14 | case class BacktestPerformanceSummary(pnl: PnL) 15 | case class DecisionDelayMillis(value: Long) extends AnyVal 16 | 17 | def originalBacktest( 18 | testDays: List[MonthDay], 19 | decisionDelay: DecisionDelayMillis): BacktestPerformanceSummary = { 20 | val pnls = for { 21 | d <- testDays 22 | _ = Thread.sleep(decisionDelay.value) 23 | } yield PnL(10) 24 | BacktestPerformanceSummary(pnls.reduceOption(PnL.merge).getOrElse(PnL.zero)) 25 | } 26 | 27 | def backtestWithoutConcurrency( 28 | testDays: List[MonthDay], 29 | decisionDelay: DecisionDelayMillis): Task[BacktestPerformanceSummary] = { 30 | val ts = for (d <- testDays) yield Task.delay { 31 | Thread.sleep(decisionDelay.value) 32 | PnL(10) 33 | } 34 | Task.gatherUnordered(ts).map(pnls => BacktestPerformanceSummary( 35 | pnls.reduceOption(PnL.merge).getOrElse(PnL.zero))) 36 | } 37 | 38 | def backtestWithAllForked( 39 | testDays: List[MonthDay], 40 | decisionDelay: DecisionDelayMillis): Task[BacktestPerformanceSummary] = { 41 | val ts = for (d <- testDays) yield Task.fork { 42 | Thread.sleep(decisionDelay.value) 43 | Task.now(PnL(10)) 44 | } 45 | Task.gatherUnordered(ts).map(pnls => BacktestPerformanceSummary( 46 | pnls.reduceOption(PnL.merge).getOrElse(PnL.zero))) 47 | } 48 | 49 | def backtestWithBatchedForking( 50 | testDays: List[MonthDay], 51 | decisionDelay: DecisionDelayMillis): Task[BacktestPerformanceSummary] = { 52 | val ts = for (d <- testDays) yield Task.delay { 53 | Thread.sleep(decisionDelay.value) 54 | PnL(10) 55 | } 56 | Task.gatherUnordered(ts.sliding(30, 30).toList.map(xs => 57 | Task.fork(Task.gatherUnordered(xs)))).map(pnls => 58 | BacktestPerformanceSummary( 59 | pnls.flatten.reduceOption(PnL.merge).getOrElse(PnL.zero))) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/backtesting/BacktestBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.backtesting 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import highperfscala.concurrency.backtesting.Backtest.{BacktestPerformanceSummary, DecisionDelayMillis} 6 | import org.joda.time.{DateTime, Interval, MonthDay} 7 | import org.openjdk.jmh.annotations.Mode._ 8 | import org.openjdk.jmh.annotations._ 9 | 10 | import scala.annotation.tailrec 11 | 12 | @BenchmarkMode(Array(Throughput)) 13 | @OutputTimeUnit(TimeUnit.SECONDS) 14 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 15 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 16 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 17 | class BacktestBenchmarks { 18 | 19 | import BacktestBenchmarks._ 20 | 21 | @Benchmark 22 | def withoutConcurrency(state: BenchmarkState): BacktestPerformanceSummary = 23 | Backtest.backtestWithoutConcurrency(state.backtestDays, state.decisionDelay) 24 | .unsafePerformSync 25 | 26 | @Benchmark 27 | def withBatchedForking(state: BenchmarkState): BacktestPerformanceSummary = 28 | Backtest.backtestWithBatchedForking(state.backtestDays, state.decisionDelay) 29 | .unsafePerformSync 30 | 31 | @Benchmark 32 | def withAllForked(state: BenchmarkState): BacktestPerformanceSummary = 33 | Backtest.backtestWithAllForked(state.backtestDays, state.decisionDelay) 34 | .unsafePerformSync 35 | } 36 | 37 | object BacktestBenchmarks { 38 | 39 | private def daysWithin(i: Interval): List[MonthDay] = { 40 | @tailrec 41 | def recurse(xs: List[MonthDay], current: DateTime): List[MonthDay] = 42 | current.isAfter(i.getEnd) match { 43 | case true => xs 44 | case false => recurse( 45 | new MonthDay(current.getMonthOfYear, current.getDayOfMonth) :: xs, 46 | current.plusDays(1)) 47 | } 48 | recurse(Nil, i.getStart) 49 | } 50 | 51 | // Constant starting point to avoid differences due to number of days 52 | // per month 53 | private val end: DateTime = new DateTime(2016, 1, 1, 0, 0, 0, 0) 54 | private def trailingMonths(backtestIntervalMonths: Int): Interval = 55 | new Interval( 56 | end.minusMonths(backtestIntervalMonths), end) 57 | 58 | @State(Scope.Benchmark) 59 | class BenchmarkState { 60 | @Param(Array("1", "10")) 61 | var decisionDelayMillis: Long = 0 62 | @Param(Array("1", "12", "24" )) 63 | var backtestIntervalMonths: Int = 0 64 | 65 | var decisionDelay: DecisionDelayMillis = DecisionDelayMillis(-1) 66 | var backtestDays: List[MonthDay] = Nil 67 | 68 | @Setup 69 | def setup(): Unit = { 70 | decisionDelay = DecisionDelayMillis(decisionDelayMillis) 71 | backtestDays = daysWithin(trailingMonths(backtestIntervalMonths)) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/backtesting/Backtesting.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.backtesting 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.joda.time.{DateTime, Interval} 6 | 7 | import scala.concurrent.{Await, Future} 8 | 9 | object Backtesting { 10 | 11 | sealed trait Strategy 12 | 13 | case class PnL(value: BigDecimal) extends AnyVal 14 | case class BacktestPerformanceSummary(pnl: PnL) 15 | 16 | case class Ticker(value: String) extends AnyVal 17 | 18 | def backtest( 19 | strategy: Strategy, 20 | ticker: Ticker, 21 | testInterval: Interval): BacktestPerformanceSummary = ??? 22 | 23 | sealed trait VectorBasedReturnSeriesFrame 24 | 25 | def loadReturns(testInterval: Interval): VectorBasedReturnSeriesFrame = ??? 26 | 27 | case object Dave1 extends Strategy 28 | case object Dave2 extends Strategy 29 | 30 | object Serial { 31 | def lastMonths(months: Int): Interval = 32 | new Interval(new DateTime().minusMonths(months), new DateTime()) 33 | backtest(Dave1, Ticker("AAPL"), lastMonths(3)) 34 | backtest(Dave1, Ticker("GOOG"), lastMonths(3)) 35 | backtest(Dave2, Ticker("AAPL"), lastMonths(3)) 36 | backtest(Dave2, Ticker("GOOG"), lastMonths(2)) 37 | } 38 | 39 | object ForComprehension { 40 | def lastMonths(months: Int): Interval = 41 | new Interval(new DateTime().minusMonths(months), new DateTime()) 42 | 43 | implicit val ec = scala.concurrent.ExecutionContext.Implicits.global 44 | val summariesF = for { 45 | firstDaveAapl <- Future(backtest(Dave1, Ticker("AAPL"), lastMonths(3))) 46 | firstDaveGoog <- Future(backtest(Dave1, Ticker("GOOG"), lastMonths(3))) 47 | secondDaveAapl <- Future(backtest(Dave2, Ticker("AAPL"), lastMonths(3))) 48 | secondDaveGoog <- Future(backtest(Dave2, Ticker("GOOG"), lastMonths(2))) 49 | } yield (firstDaveAapl, firstDaveGoog, secondDaveAapl, secondDaveGoog) 50 | 51 | Await.result(summariesF, scala.concurrent.duration.Duration(1, TimeUnit.SECONDS)) 52 | 53 | Future(1).flatMap(f1 => Future(2).flatMap(f2 => Future(3).map(f3 => (f1, f2, f3)))) 54 | } 55 | 56 | object Concurrency { 57 | def lastMonths(months: Int): Interval = 58 | new Interval(new DateTime().minusMonths(months), new DateTime()) 59 | 60 | implicit val ec = scala.concurrent.ExecutionContext.Implicits.global 61 | val firstDaveAaplF = Future(backtest(Dave1, Ticker("AAPL"), lastMonths(3))) 62 | val firstDaveGoogF = Future(backtest(Dave1, Ticker("GOOG"), lastMonths(3))) 63 | val secondDaveAaplF = Future(backtest(Dave2, Ticker("AAPL"), lastMonths(3))) 64 | val secondDaveGoogF = Future(backtest(Dave2, Ticker("GOOG"), lastMonths(2))) 65 | val z = for { 66 | firstDaveAapl <- firstDaveAaplF 67 | firstDaveGoog <- firstDaveGoogF 68 | secondDaveAapl <- secondDaveAaplF 69 | secondDaveGoog <- secondDaveGoogF 70 | } yield (firstDaveAapl, firstDaveGoog, secondDaveAapl, secondDaveGoog) 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/blocking/BlockingExample.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.blocking 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | object BlockingExample { 6 | 7 | case class Ticker(value: String) extends AnyVal 8 | 9 | case class ClientId(value: Long) extends AnyVal 10 | 11 | case class Order(ticker: Ticker, clientId: ClientId) 12 | 13 | object Order { 14 | 15 | val staticList = List( 16 | Order(Ticker("FOO"), ClientId(12345)), 17 | Order(Ticker("FOO"), ClientId(5437)), 18 | Order(Ticker("BAR"), ClientId(12345)), 19 | Order(Ticker("FOO"), ClientId(9864)), 20 | Order(Ticker("BAR"), ClientId(12345)), 21 | Order(Ticker("BAR"), ClientId(5680)), 22 | Order(Ticker("BAR"), ClientId(12345)), 23 | Order(Ticker("FOO"), ClientId(542467)) 24 | ) 25 | } 26 | 27 | object JdbcOrderRepository { 28 | 29 | def findBuyOrders( 30 | client: ClientId, 31 | ticker: Ticker)(implicit ec: ExecutionContext): Future[List[Order]] = Future { 32 | Thread.sleep(100) 33 | Order.staticList.filter(o => o.clientId == client && o.ticker == ticker) 34 | }(ec) 35 | 36 | import scala.concurrent.ExecutionContext.Implicits.global 37 | 38 | def findBuyOrdersWithBlocking( 39 | client: ClientId, 40 | ticker: Ticker): Future[List[Order]] = Future { 41 | scala.concurrent.blocking { 42 | Thread.sleep(100) 43 | Order.staticList.filter(o => o.clientId == client && o.ticker == ticker) 44 | } 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/blocking/BlockingFutureBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.blocking 2 | 3 | import java.util.concurrent.{ExecutorService, Executors, TimeUnit} 4 | 5 | import highperfscala.concurrency.blocking.BlockingExample.{ClientId, Order, Ticker} 6 | import org.openjdk.jmh.annotations.Mode.Throughput 7 | import org.openjdk.jmh.annotations._ 8 | 9 | import scala.concurrent.duration.Duration 10 | import scala.concurrent.{Await, ExecutionContext, Future} 11 | 12 | @BenchmarkMode(Array(Throughput)) 13 | @OutputTimeUnit(TimeUnit.SECONDS) 14 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 15 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 16 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 17 | class BlockingFutureBenchmarks { 18 | 19 | import BlockingFutureBenchmarks._ 20 | 21 | @Benchmark 22 | def withDefaultContext(state: BlockingFutureState): List[List[Order]] = { 23 | val futures = (1 until state.operations).map{_ => 24 | BlockingExample.JdbcOrderRepository.findBuyOrders( 25 | state.clientId, state.ticker 26 | )(state.defaultC) 27 | } 28 | 29 | implicit val ex = state.defaultC 30 | Await.result( 31 | Future.sequence(futures).map(_.toList), 32 | Duration("5 minutes") 33 | ) 34 | } 35 | 36 | @Benchmark 37 | def withDedicatedContext(state: BlockingFutureState): List[List[Order]] = { 38 | val futures = (1 until state.operations).map{_ => 39 | BlockingExample.JdbcOrderRepository.findBuyOrders( 40 | state.clientId, state.ticker 41 | )(state.dedicatedC) 42 | } 43 | 44 | implicit val ex = state.defaultC // we use CPU-bound context for computations below 45 | Await.result( 46 | Future.sequence(futures).map(_.toList), 47 | Duration("5 minutes") 48 | ) 49 | } 50 | 51 | } 52 | 53 | object BlockingFutureBenchmarks { 54 | 55 | @State(Scope.Benchmark) 56 | class BlockingFutureState { 57 | 58 | @Param(Array("10", "1000")) 59 | var operations: Int = 0 60 | 61 | val clientId = ClientId(12345) 62 | val ticker = Ticker("FOO") 63 | 64 | var defaultC: ExecutionContext = null 65 | var dedicatedC: ExecutionContext = null 66 | var es: ExecutorService = null 67 | 68 | @Setup(Level.Trial) 69 | def setup(): Unit = { 70 | defaultC = scala.concurrent.ExecutionContext.global 71 | es = { 72 | val i = Runtime.getRuntime.availableProcessors * 20 73 | Executors.newFixedThreadPool(i) 74 | } 75 | dedicatedC = ExecutionContext.fromExecutorService(es) 76 | } 77 | 78 | @TearDown(Level.Trial) 79 | def tearDown(): Unit = { 80 | es.shutdownNow() 81 | } 82 | 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/future/FutureErrorHandling.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.future 2 | 3 | import scala.concurrent.Future 4 | import scala.util.{Failure, Success} 5 | import scalaz.{\/-, \/} 6 | 7 | object FutureErrorHandling { 8 | def main(args: Array[String]): Unit = { 9 | implicit val context = scala.concurrent.ExecutionContext.global 10 | def failedTransform(): Unit = { 11 | Future("not-an-integer").map(_.toInt).map(i => { 12 | println("Multiplying") 13 | i * 2 14 | }) 15 | Thread.sleep(1000) 16 | } 17 | 18 | def recoverWith(): Unit = { 19 | Future("not-an-integer").map(_.toInt).recover { 20 | case _: NumberFormatException => -2 21 | }.map(i => { 22 | println("Multiplying") 23 | i * 2 24 | }).onComplete { 25 | case Success(i) => println(s"Multiplication result = $i") 26 | case Failure(e) => println(s"Failed due to ${e.getMessage}") 27 | } 28 | Thread.sleep(1000) 29 | } 30 | 31 | def disjunction(): Unit = { 32 | scalaz.\/.right[Throwable, Int](1).map(_ * 2) 33 | 34 | } 35 | 36 | recoverWith() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/future/FutureExample.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.future 2 | 3 | object FutureExample { 4 | 5 | def main(args: Array[String]): Unit = { 6 | import scala.concurrent.Future 7 | implicit val context = scala.concurrent.ExecutionContext.global 8 | Future(1).map(_ + 1).filter(_ % 2 == 0).foreach(println) 9 | 10 | Future(1).map(i => { 11 | println(Thread.currentThread().getName) 12 | i + 1 13 | }).filter(i => { 14 | println(Thread.currentThread().getName) 15 | i % 2 == 0 16 | }).foreach(println) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/future/OrderSubmission.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.future 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | import scala.util.{Failure, Success} 5 | 6 | object OrderSubmission { 7 | 8 | trait RawOrder 9 | trait OrderSubmitted 10 | 11 | trait ValidatedOrder 12 | object ValidatedOrder { 13 | def fromRawOrder(o: RawOrder): Option[ValidatedOrder] = None 14 | } 15 | 16 | trait AccountPositions 17 | 18 | def submitOrder( 19 | ec: ExecutionContext, 20 | sendToExchange: ValidatedOrder => Future[OrderSubmitted], 21 | updatePositions: OrderSubmitted => Future[AccountPositions], 22 | o: RawOrder): Unit = { 23 | implicit val iec = ec 24 | 25 | (for { 26 | vo <- ValidatedOrder.fromRawOrder(o).fold(Future.failed[ValidatedOrder]( 27 | new Exception("Order failed validation")))(Future.successful) 28 | os <- sendToExchange(vo) 29 | ap <- updatePositions(os) 30 | } yield (os, ap)).onComplete { 31 | case Success((os, ap)) => // Marshal order submission info to caller 32 | case Failure(e) => // Marshal appropriate error response to caller 33 | } 34 | } 35 | 36 | def submitOrderWithMetrics( 37 | ec: ExecutionContext, 38 | sendToExchange: ValidatedOrder => Future[OrderSubmitted], 39 | updatePositions: OrderSubmitted => Future[AccountPositions], 40 | incrementExchangeErrorCount: () => Unit, 41 | o: RawOrder): Unit = { 42 | implicit val iec = ec 43 | 44 | (for { 45 | vo <- ValidatedOrder.fromRawOrder(o).fold(Future.failed[ValidatedOrder]( 46 | new Exception("Order failed validation")))(Future.successful) 47 | os <- { 48 | val f = sendToExchange(vo) 49 | f.onFailure({ case e => incrementExchangeErrorCount() }) 50 | f 51 | } 52 | ap <- updatePositions(os) 53 | } yield (os, ap)).onComplete { 54 | case Success((os, ap)) => // Marshal order submission info to caller 55 | case Failure(e) => // Marshal appropriate error response to caller 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/future/SafeAwait.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.future 2 | 3 | import java.util.concurrent.TimeoutException 4 | 5 | import scala.concurrent.duration.Duration 6 | import scala.concurrent.{Awaitable, Await} 7 | import scala.util.{Failure, Success, Try} 8 | 9 | object SafeAwait { 10 | def result[T]( 11 | awaitable: Awaitable[T], 12 | atMost: Duration): Option[T] = Try(Await.result(awaitable, atMost)) match { 13 | case Success(t) => Some(t) 14 | case Failure(_: TimeoutException) => None 15 | case Failure(e) => throw e 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/future/TransformFutureBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.future 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations.Mode.Throughput 6 | import org.openjdk.jmh.annotations._ 7 | 8 | import scala.concurrent.duration.Duration 9 | import scala.concurrent.{Await, Future} 10 | 11 | @BenchmarkMode(Array(Throughput)) 12 | @OutputTimeUnit(TimeUnit.SECONDS) 13 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 14 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 15 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 16 | class TransformFutureBenchmarks { 17 | 18 | import TransformFutureBenchmarks._ 19 | 20 | @Benchmark 21 | def manyTransforms(state: TransformFutureState): Int = { 22 | import scala.concurrent.ExecutionContext.Implicits._ 23 | val init = Future(0) 24 | val res = (1 until state.operations).foldLeft(init)((f, _) => f.map(_ + 1)) 25 | Await.result(res, Duration("5 minutes")) 26 | } 27 | 28 | @Benchmark 29 | def oneTransform(state: TransformFutureState): Int = { 30 | import scala.concurrent.ExecutionContext.Implicits._ 31 | val res = Future { 32 | (1 until state.operations).foldLeft(0)((acc, _) => acc + 1) 33 | } 34 | Await.result(res, Duration("5 minutes")) 35 | } 36 | 37 | } 38 | 39 | object TransformFutureBenchmarks { 40 | 41 | @State(Scope.Benchmark) 42 | class TransformFutureState { 43 | 44 | @Param(Array("5", "10")) 45 | var operations: Int = 0 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/task/TaskExample.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.task 2 | 3 | import scala.concurrent.duration.Duration 4 | import scala.concurrent.{Await, ExecutionContext, Future, Promise} 5 | import scala.util.{Failure, Success} 6 | import scalaz.concurrent.Task 7 | import scalaz.{-\/, \/, \/-} 8 | 9 | object TaskExample { 10 | 11 | def createAndRunTask(): Unit = { 12 | val t = Task { 13 | println("Computing the answer...") 14 | Thread.sleep(2000) 15 | 40 + 2 16 | } 17 | 18 | t.unsafePerformAsync { 19 | case \/-(answer) => println("The answer is " + answer) 20 | case -\/(ex) => println("Failed to compute the answer: " + ex) 21 | } 22 | 23 | println("Waiting for the answer") 24 | } 25 | 26 | object CallbackAPI { 27 | 28 | import scala.concurrent.ExecutionContext.Implicits.global 29 | 30 | def doCoolThings[A](a: => A, f: (Throwable \/ A) => Unit): Unit = 31 | Future(a).onComplete { 32 | case Failure(ex) => f(-\/(ex)) 33 | case Success(res) => f(\/-(res)) 34 | } 35 | } 36 | 37 | def doCoolThingsToTask[A](a: => A): Task[A] = 38 | Task.async { f => 39 | CallbackAPI.doCoolThings[A](a, res => f(res)) 40 | } 41 | 42 | def futureToTask[A](future: Future[A])(implicit ec: ExecutionContext): Task[A] = 43 | Task.async { f => 44 | future.onComplete { 45 | case Success(res) => f(\/-(res)) 46 | case Failure(ex) => f(-\/(ex)) 47 | } 48 | } 49 | 50 | def taskToFuture[A](t: Task[A]): Future[A] = { 51 | val p = Promise[A]() 52 | t.unsafePerformAsync { 53 | case \/-(a) => p.success(a) 54 | case -\/(ex) => p.failure(ex) 55 | } 56 | p.future 57 | } 58 | 59 | def mapFuture() = { 60 | import scala.concurrent.ExecutionContext.Implicits.global 61 | println("Main thread: " + Thread.currentThread.getName) 62 | val f = Future { 63 | println("First execution: " + Thread.currentThread.getName) 64 | 40 65 | } 66 | f.map { i => 67 | println("Second execution: " + Thread.currentThread.getName) 68 | i + 2 69 | } 70 | Await.result(f, Duration("1 second")) 71 | } 72 | 73 | def flatMapFuture() = { 74 | import scala.concurrent.ExecutionContext.Implicits.global 75 | println("Main thread: " + Thread.currentThread.getName) 76 | val f = Future { 77 | println("First execution: " + Thread.currentThread.getName) 78 | 40 79 | } 80 | f.flatMap { i => Future { 81 | println("Second execution: " + Thread.currentThread.getName) 82 | i + 2 83 | } 84 | } 85 | Await.result(f, Duration("1 second")) 86 | } 87 | 88 | def mapTask() = { 89 | println("Main thread: " + Thread.currentThread.getName) 90 | val f = Task { 91 | println("First execution: " + Thread.currentThread.getName) 92 | 40 93 | } 94 | f.map { i => 95 | println("Second execution: " + Thread.currentThread.getName) 96 | i + 2 97 | }.unsafePerformSync 98 | } 99 | 100 | def flatMapTask() = { 101 | println("Main thread: " + Thread.currentThread.getName) 102 | val f = Task { 103 | println("First execution: " + Thread.currentThread.getName) 104 | 40 105 | } 106 | f.flatMap { i => Task { 107 | println("Second execution: " + Thread.currentThread.getName) 108 | i + 2 109 | } 110 | }.unsafePerformSync 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /chapter6/src/main/scala/highperfscala/concurrency/task/TaskFutureBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.concurrency.task 2 | 3 | import java.util.concurrent.{ExecutorService, Executors, TimeUnit} 4 | 5 | import org.openjdk.jmh.annotations.Mode.Throughput 6 | import org.openjdk.jmh.annotations._ 7 | 8 | import scala.concurrent.{ExecutionContext, Future, Await} 9 | import scala.concurrent.duration.Duration 10 | import scalaz.concurrent.Task 11 | 12 | @BenchmarkMode(Array(Throughput)) 13 | @OutputTimeUnit(TimeUnit.SECONDS) 14 | @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) 15 | @Measurement(iterations = 30, time = 10, timeUnit = TimeUnit.SECONDS) 16 | @Fork(value = 1, warmups = 1, jvmArgs = Array("-Xms1G", "-Xmx1G")) 17 | class TaskFutureBenchmarks { 18 | 19 | import TaskFutureBenchmarks._ 20 | 21 | @Benchmark 22 | def mapWithFuture(state: TaskFutureState): Int = { 23 | implicit val ec = state.context 24 | val init = Future(0) 25 | val res = (1 until state.operations).foldLeft(init)((f, _) => f.map(_ + 1)) 26 | Await.result(res, Duration("5 minutes")) 27 | } 28 | 29 | @Benchmark 30 | def mapWithTask(state: TaskFutureState): Int = { 31 | val init = Task(0)(state.es) 32 | val res = (1 until state.operations).foldLeft(init)((t, _) => t.map(_ + 1)) 33 | res.unsafePerformSync 34 | } 35 | 36 | @Benchmark 37 | def flatMapWithFuture(state: TaskFutureState): Int = { 38 | implicit val ec = state.context 39 | val init = Future(0) 40 | val res = (1 until state.operations).foldLeft(init)((f, _) => 41 | f.flatMap(i => Future(i + 1))) 42 | Await.result(res, Duration("5 minutes")) 43 | } 44 | 45 | @Benchmark 46 | def flatMapWithTask(state: TaskFutureState): Int = { 47 | val init = Task(0)(state.es) 48 | val res = (1 until state.operations).foldLeft(init)((t, _) => 49 | t.flatMap(i => Task(i + 1)(state.es))) 50 | res.unsafePerformSync 51 | } 52 | 53 | } 54 | 55 | object TaskFutureBenchmarks { 56 | 57 | @State(Scope.Benchmark) 58 | class TaskFutureState { 59 | 60 | @Param(Array("5", "10", "100")) 61 | var operations: Int = 0 62 | 63 | var es: ExecutorService = null 64 | var context: ExecutionContext = null 65 | 66 | @Setup(Level.Trial) 67 | def setup(): Unit = { 68 | es = Executors.newFixedThreadPool(20) 69 | context = ExecutionContext.fromExecutor(es) 70 | } 71 | 72 | @TearDown(Level.Trial) 73 | def tearDown(): Unit = { 74 | es.shutdownNow() 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /chapter6/target/.history: -------------------------------------------------------------------------------- 1 | console 2 | project chapter6 3 | console 4 | compile 5 | console 6 | compile 7 | console 8 | jmh:run TaskFutureBenchmarks 9 | compile 10 | consol 11 | console 12 | jmh:run TaskFutureBenchmarks 13 | jmh:run BlockingFutureBenchmarks 14 | jmh:run TaskFutureBenchmarks 15 | jmh:run TransformFutureBenchmarks 16 | clean 17 | compile 18 | jmh:run TaskFutureBenchmarks 19 | jmh:run TransformFutureBenchmarks 20 | jmh:run TaskFutureBenchmarks 21 | -------------------------------------------------------------------------------- /chapter6/target/streams/$global/clean/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter6/target/streams/$global/clean/$global/streams/out -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/crdt/Counters.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.crdt 2 | 3 | case class CounterUpdate(i: Int) 4 | 5 | case class GCounterState(uid: Int, counter: Int) 6 | 7 | class StateBasedGCounter( 8 | val uid: Int, 9 | count: Int, 10 | otherCounters: Map[Int, Int]) { 11 | 12 | def value: Int = count + otherCounters.values.sum 13 | 14 | def update( 15 | change: CounterUpdate): (StateBasedGCounter, GCounterState) = { 16 | (new StateBasedGCounter(uid, count + change.i, otherCounters), 17 | GCounterState(uid, count)) 18 | } 19 | 20 | def merge(other: GCounterState): StateBasedGCounter = { 21 | val newValue = other.counter max otherCounters.getOrElse(other.uid, 0) 22 | new StateBasedGCounter(uid, count, otherCounters.+(other.uid -> newValue)) 23 | } 24 | } 25 | 26 | class OperationBasedCounter(count: Int) { 27 | 28 | def value: Int = count 29 | 30 | def update(change: CounterUpdate): (OperationBasedCounter, CounterUpdate) = 31 | new OperationBasedCounter(count + change.i) -> change 32 | 33 | def merge(operation: CounterUpdate): OperationBasedCounter = 34 | update(operation)._1 35 | 36 | } 37 | 38 | case class PNCounterState( 39 | incState: GCounterState, 40 | decState: GCounterState) 41 | 42 | object StateBasedPNCounter { 43 | def newCounter(uid: Int): StateBasedPNCounter = 44 | new StateBasedPNCounter( 45 | new StateBasedGCounter(uid, 0, Map.empty), 46 | new StateBasedGCounter(uid, 0, Map.empty) 47 | ) 48 | } 49 | 50 | class StateBasedPNCounter private( 51 | incCounter: StateBasedGCounter, 52 | decCounter: StateBasedGCounter) { 53 | 54 | def value = incCounter.value - decCounter.value 55 | 56 | def update(change: CounterUpdate): (StateBasedPNCounter, PNCounterState) = { 57 | val (newIncCounter, newDecCounter, stateUpdate) = 58 | change match { 59 | case CounterUpdate(c) if c >= 0 => 60 | val (iC, iState) = incCounter.update(change) 61 | val dState = GCounterState(decCounter.uid, decCounter.value) 62 | (iC, decCounter, PNCounterState(iState, dState)) 63 | case CounterUpdate(c) if c < 0 => 64 | val (dC, dState) = decCounter.update(change) 65 | val iState = GCounterState(incCounter.uid, incCounter.value) 66 | (incCounter, dC, PNCounterState(iState, dState)) 67 | } 68 | 69 | (new StateBasedPNCounter(newIncCounter, newDecCounter), stateUpdate) 70 | } 71 | 72 | def merge(other: PNCounterState): StateBasedPNCounter = 73 | new StateBasedPNCounter( 74 | incCounter.merge(other.incState), 75 | decCounter.merge(other.decState) 76 | ) 77 | } -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/crdt/sets.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.crdt 2 | 3 | class OperationBasedGSet[A](set: Set[A]) { 4 | 5 | def value = set 6 | 7 | def contains(a: A) = set.contains(a) 8 | 9 | def update(a: AddElement[A]): (OperationBasedGSet[A], AddElement[A]) = 10 | (new OperationBasedGSet(set + a.a), a) 11 | 12 | def merge(other: AddElement[A]): OperationBasedGSet[A] = 13 | new OperationBasedGSet(set + other.a) 14 | 15 | } 16 | 17 | case class AddElement[A](a: A) 18 | 19 | case class GSetState[A](set: Set[A]) 20 | 21 | class StateBasedGSet[A](set: Set[A]) { 22 | 23 | def value: Set[A] = set 24 | 25 | def contains(a: A): Boolean = set.contains(a) 26 | 27 | def update(a: AddElement[A]): (StateBasedGSet[A], GSetState[A]) = { 28 | val newSet = new StateBasedGSet(set + a.a) 29 | (newSet, GSetState(newSet.value)) 30 | } 31 | 32 | def merge(other: GSetState[A]): StateBasedGSet[A] = { 33 | new StateBasedGSet(set ++ other.set) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/BboUpdatedBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | import java.util.concurrent.atomic.{AtomicBoolean, AtomicLong} 4 | import java.util.concurrent.{LinkedBlockingQueue, TimeUnit} 5 | 6 | import highperfscala.free.GenericBenchmark.{BenchmarkIterationCount, MessageSentTimestamp, MessagesPerSecond} 7 | import org.mpierce.metrics.reservoir.hdrhistogram.HdrHistogramReservoir 8 | 9 | import scala.language.postfixOps 10 | 11 | object BboUpdatedBenchmark { 12 | 13 | private def generateDeterministicSample(): List[BboUpdated] = { 14 | val t = Ticker("XYZ") 15 | (1 to 5000).foldLeft(List.empty[BboUpdated]) { 16 | case (acc, i) => 17 | val event = acc.headOption match { 18 | case Some(h) => i % 2 == 0 match { 19 | case true => BboUpdated( 20 | t, Bid(h.bid.value + 0.41), Offer(h.bid.value + 0.29)) 21 | case false => BboUpdated( 22 | t, Bid(h.bid.value - 0.35), Offer(h.bid.value - 0.25)) 23 | } 24 | case None => BboUpdated(t, Bid(22.45), Offer(23.51)) 25 | } 26 | (event.bid.value >= event.offer.value match { 27 | case true => event.copy(offer = Offer(event.bid.value + 0.47)) 28 | case false => event 29 | }) :: acc 30 | } 31 | } 32 | 33 | 34 | sealed trait ExecutionStrategy 35 | case object WithoutFree extends ExecutionStrategy 36 | case object WithThunk extends ExecutionStrategy 37 | case object WithTask extends ExecutionStrategy 38 | 39 | def main(args: Array[String]): Unit = { 40 | val histogram = new HdrHistogramReservoir() 41 | val queue = new LinkedBlockingQueue[(MessageSentTimestamp, BboUpdated)](100) 42 | val isRecording = new AtomicBoolean(false) 43 | val isDoneProcessing = new AtomicBoolean(false) 44 | 45 | if (args.length != 3) sys.error("Missing required args") 46 | val mps = MessagesPerSecond(args(0).toInt) 47 | val bic = BenchmarkIterationCount(args(1).toInt) 48 | val executionStrategy = args(2) match { 49 | case "without-free" => WithoutFree 50 | case "with-thunk" => WithThunk 51 | case "with-task" => WithTask 52 | case unsupported => sys.error( 53 | s"Unsupported execution strategy: $unsupported") 54 | } 55 | 56 | { 57 | val eventThread = new Thread(new Runnable { 58 | val count = new AtomicLong(0) 59 | val strategy = new ProductionStrategy(new AtomicLong(0)) 60 | def run(): Unit = while (true) { 61 | def updateHistogram(l: ProcessingLatencyMs): Unit = 62 | if (isRecording.get()) histogram.update(l.value) 63 | 64 | Option(queue.poll(5, TimeUnit.SECONDS)) match { 65 | case Some((ts, e)) => executionStrategy match { 66 | case WithoutFree => BboUpdatedPipeline.strategyPipeline( 67 | updateHistogram, strategy, ts, e) 68 | case WithThunk => BboUpdatedPipeline.runWithFoldInterpreter( 69 | updateHistogram, strategy, ts, e) 70 | case WithTask => BboUpdatedPipeline.runWithTaskInterpreter( 71 | updateHistogram, strategy, ts, e) 72 | } 73 | case None => isDoneProcessing.set(true) 74 | } 75 | } 76 | }) 77 | eventThread.setDaemon(true) 78 | eventThread.start() 79 | } 80 | 81 | def afterWarmUp(): Unit = { 82 | while (!isDoneProcessing.get()) Thread.sleep(1000) 83 | 84 | isRecording.set(true) 85 | isDoneProcessing.set(false) 86 | } 87 | 88 | GenericBenchmark.runBenchmark[BboUpdated, Option[Either[Bid, Offer]]]( 89 | generateDeterministicSample(), mps, bic, 90 | (None, (ts, acc, e) => { 91 | queue.put((ts, e)) 92 | None 93 | }), 94 | afterWarmUp, 95 | (ts, e) => queue.offer((ts, e))) 96 | 97 | while (!isDoneProcessing.get()) Thread.sleep(1000) 98 | 99 | GenericBenchmark.printSnapshot(histogram.getSnapshot) 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/BboUpdatedPipeline.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | import highperfscala.free.GenericBenchmark.MessageSentTimestamp 4 | 5 | import scala.concurrent.duration._ 6 | import scala.language.{higherKinds, postfixOps} 7 | import scalaz.concurrent.Task 8 | import scalaz.{-\/, \/-, ~>} 9 | 10 | object BboUpdatedPipeline { 11 | 12 | sealed trait BboProcessingFailure 13 | object BboProcessingFailure { 14 | def enrichmentFailure(t: Ticker): BboProcessingFailure = 15 | EnrichmentFailure(t) 16 | def journalingFailure: BboProcessingFailure = JournalingFailure 17 | def tradeAuthorizationFailure: BboProcessingFailure = TradeAuthorizationFailure 18 | } 19 | 20 | case class EnrichmentFailure(t: Ticker) extends BboProcessingFailure 21 | case object JournalingFailure extends BboProcessingFailure 22 | case object TradeAuthorizationFailure extends BboProcessingFailure 23 | 24 | import BboProcessingFailure._ 25 | 26 | def enrichEvent(e: BboUpdated): BboUpdated = e 27 | def journalEvent(e: BboUpdated): Unit = () 28 | def performPreTradeBalanceChecks(e: BboUpdated): Unit = () 29 | def sendTradingDecision(d: Either[Bid, Offer]): Unit = () 30 | 31 | def strategyPipeline( 32 | recordProcessingLatency: ProcessingLatencyMs => Unit, 33 | s: TradingStrategy, 34 | ts: MessageSentTimestamp, 35 | e: BboUpdated): Unit = { 36 | val enriched = enrichEvent(e) 37 | journalEvent(enriched) 38 | performPreTradeBalanceChecks(enriched) 39 | val d = s.makeTradingDecision(enriched) 40 | d.foreach(sendTradingDecision) 41 | recordProcessingLatency(ProcessingLatencyMs( 42 | System.currentTimeMillis() - ts.value)) 43 | } 44 | 45 | private val pipeline = for { 46 | enriched <- StartWith(enrichEvent) within (8 millis) orElse (e => 47 | enrichmentFailure(e.ticker)) 48 | _ <- Step(journalEvent(enriched)) within (9 millis) orElse 49 | journalingFailure 50 | _ <- Step(performPreTradeBalanceChecks(enriched)) within (10 millis) orElse 51 | tradeAuthorizationFailure 52 | decision <- MakeTradingDecision(enriched) 53 | } yield decision 54 | 55 | case class PipelineState( 56 | ts: MessageSentTimestamp, 57 | strategy: TradingStrategy, 58 | event: BboUpdated) 59 | 60 | private def logFailure(f: BboProcessingFailure): Unit = () 61 | private def logException(e: Throwable): Unit = () 62 | 63 | private def hasProcessingTimeExpired( 64 | ts: MessageSentTimestamp, l: LimitMs): Boolean = 65 | System.currentTimeMillis() - ts.value >= l.value 66 | 67 | def runWithFoldInterpreter( 68 | recordProcessingLatency: ProcessingLatencyMs => Unit, 69 | strategy: TradingStrategy, 70 | ts: MessageSentTimestamp, 71 | e: BboUpdated): Unit = { 72 | val (_, decision) = pipeline.free.foldRun( 73 | PipelineState(ts, strategy, e)) { 74 | case (state, StartProcessing(whenActive, whenExpired, limitMs)) => 75 | state -> (hasProcessingTimeExpired(state.ts, limitMs) match { 76 | case true => whenExpired(e) 77 | case false => whenActive(e) 78 | }) 79 | case (state, Timed(whenActive, whenExpired, limitMs)) => 80 | state -> (hasProcessingTimeExpired(state.ts, limitMs) match { 81 | case true => whenExpired() 82 | case false => whenActive() 83 | }) 84 | case (state, TradingDecision(runStrategy)) => 85 | state -> runStrategy(state.strategy) 86 | } 87 | 88 | decision.fold(logFailure, { 89 | case Some(order) => 90 | sendTradingDecision(order) 91 | recordProcessingLatency(ProcessingLatencyMs( 92 | System.currentTimeMillis() - ts.value)) 93 | case None => 94 | recordProcessingLatency(ProcessingLatencyMs( 95 | System.currentTimeMillis() - ts.value)) 96 | }) 97 | } 98 | 99 | private def thunkToTask(ps: PipelineState): Thunk ~> Task = new (Thunk ~> Task) { 100 | def apply[B](t: Thunk[B]): Task[B] = t match { 101 | case StartProcessing(whenActive, whenExpired, limitMs) => Task.suspend( 102 | hasProcessingTimeExpired(ps.ts, limitMs) match { 103 | case true => Task.now(whenExpired(ps.event)) 104 | case false => Task.now(whenActive(ps.event)) 105 | }) 106 | case Timed(whenActive, whenExpired, limitMs) => Task.suspend( 107 | hasProcessingTimeExpired(ps.ts, limitMs) match { 108 | case true => Task.now(whenExpired()) 109 | case false => Task.now(whenActive()) 110 | }) 111 | case TradingDecision(runStrategy) => 112 | Task.fork(Task.now(runStrategy(ps.strategy))) 113 | } 114 | } 115 | 116 | def runWithTaskInterpreter( 117 | recordProcessingLatency: ProcessingLatencyMs => Unit, 118 | s: TradingStrategy, 119 | ts: MessageSentTimestamp, 120 | e: BboUpdated) = { 121 | pipeline.free.foldMap(thunkToTask(PipelineState(ts, s, e))) 122 | .unsafePerformAsync { 123 | case -\/(ex) => logException(ex) 124 | case \/-(\/-(decision)) => 125 | decision.foreach(sendTradingDecision) 126 | recordProcessingLatency(ProcessingLatencyMs( 127 | System.currentTimeMillis() - ts.value)) 128 | case \/-(-\/(failure)) => logFailure(failure) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/EitherFree.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | import scalaz.{Free, \/} 4 | import language.higherKinds 5 | 6 | class EitherFree[A[_], L, R](val free: Free[A, L \/ R]) { 7 | def map[RR](f: R => RR): EitherFree[A, L, RR] = 8 | new EitherFree(free.map(_.map(f))) 9 | def flatMap[RR](f: R => EitherFree[A, L, RR]): EitherFree[A, L, RR] = 10 | new EitherFree(free.flatMap(_.fold(l => 11 | Free.point[A, L \/ RR](\/.left[L, RR](l)), z => f(z).free))) 12 | } 13 | -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/GenericBenchmark.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | import com.codahale.metrics.Snapshot 4 | import org.slf4s.Logging 5 | 6 | object GenericBenchmark extends Logging { 7 | private def infiniteCommands[A](sample: List[A]): Stream[A] = 8 | Stream.continually(sample.toStream).flatten 9 | 10 | private def generateCount[A](sample: List[A], count: Int): List[A] = 11 | infiniteCommands(sample).take(count).toList 12 | 13 | private def jvmWarmUp[A, B]( 14 | sample: List[A], 15 | init: B, 16 | f: (MessageSentTimestamp, B, A) => B): Unit = { 17 | log.debug("Begin warm up") 18 | val commands = generateCount(sample, 100000) 19 | commands.foldLeft(init) { 20 | case (acc, a) => 21 | f(MessageSentTimestamp(System.currentTimeMillis()), acc, a) 22 | } 23 | log.debug(s"End warm up") 24 | } 25 | 26 | def printSnapshot(s: Snapshot): Unit = println { 27 | s""" 28 | |Processed ${s.size} commands 29 | |mean latency: ${s.getMean} ms 30 | |median latency: ${s.getMedian} ms 31 | |75p latency: ${s.get75thPercentile()} ms 32 | |99p latency: ${s.get99thPercentile()} ms 33 | |99.9p latency: ${s.get999thPercentile()} ms 34 | |Maximum latency: ${s.getMax} ms 35 | """.stripMargin 36 | } 37 | 38 | case class MessagesPerSecond(value: Int) extends AnyVal 39 | case class BenchmarkIterationCount(value: Int) extends AnyVal 40 | case class MessageSentTimestamp(value: Long) extends AnyVal 41 | 42 | def runBenchmark[A, B]( 43 | sampleMessages: List[A], 44 | mps: MessagesPerSecond, 45 | count: BenchmarkIterationCount, 46 | warmup: (B, (MessageSentTimestamp, B, A) => B), 47 | afterWarmUp: () => Unit, 48 | handleEvent: (MessageSentTimestamp, A) => Unit): Unit = { 49 | val totalMessageCount = mps.value * count.value 50 | 51 | Function.tupled(jvmWarmUp( 52 | sampleMessages, _: B, _: (MessageSentTimestamp, B, A) => B))(warmup) 53 | 54 | afterWarmUp() 55 | 56 | var messagesWithOffset = 57 | generateCount(sampleMessages, totalMessageCount) 58 | .grouped(mps.value) 59 | .toList.zipWithIndex 60 | .flatMap { 61 | case (secondBatch, sBatchIndex) => 62 | val batchOffsetInMs = sBatchIndex * 1000 63 | val messageIntervalInMs = 1000.0 / mps.value 64 | secondBatch.zipWithIndex.map { 65 | case (command, commandIndex) => 66 | val commandOffsetInMs = 67 | Math.floor(messageIntervalInMs * commandIndex).toInt 68 | (command, batchOffsetInMs + commandOffsetInMs) 69 | } 70 | } 71 | 72 | val testStart = System.currentTimeMillis() 73 | 74 | while (messagesWithOffset.nonEmpty) { 75 | val (message, offsetInMs) = messagesWithOffset.head 76 | val shouldStart = testStart + offsetInMs 77 | 78 | while (shouldStart > System.currentTimeMillis()) { 79 | // keep the thread busy while waiting for the next batch to be sent 80 | } 81 | 82 | handleEvent(MessageSentTimestamp(shouldStart), message) 83 | messagesWithOffset = messagesWithOffset.tail 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/ProcessingLatencyMs.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | case class ProcessingLatencyMs(value: Long) extends AnyVal 4 | -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/dsl.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | import highperfscala.free.BboUpdatedPipeline.BboProcessingFailure 4 | 5 | import scala.concurrent.duration.Duration 6 | import Thunk._ 7 | 8 | class TimedStep[R](f: => R, d: Duration) { 9 | def orElse[L](l: => L): EitherFree[Thunk, L, R] = 10 | new EitherFree(timed(() => f, () => l, LimitMs(d.toMillis))) 11 | } 12 | 13 | class Step[R](f: => R) { 14 | def within(d: Duration): TimedStep[R] = new TimedStep[R](f, d) 15 | } 16 | object Step { 17 | def apply[R](f: => R): Step[R] = new Step(f) 18 | } 19 | 20 | class TimedStartingStep[R](f: BboUpdated => R, d: Duration) { 21 | def orElse[L](b: BboUpdated => L): EitherFree[Thunk, L, R] = 22 | new EitherFree(startProcessing(f, b, LimitMs(d.toMillis))) 23 | } 24 | 25 | class StartWith[R](f: BboUpdated => R) { 26 | def within(d: Duration): TimedStartingStep[R] = new TimedStartingStep[R](f, d) 27 | } 28 | object StartWith { 29 | def apply[R](f: BboUpdated => R): StartWith[R] = new StartWith(f) 30 | } 31 | 32 | object MakeTradingDecision { 33 | def apply(e: BboUpdated): EitherFree[Thunk, BboProcessingFailure, Option[Either[Bid, Offer]]] = 34 | new EitherFree(tradingDecision(_.makeTradingDecision(e))) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/model.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | case class Bid(value: BigDecimal) extends AnyVal 4 | case class Offer(value: BigDecimal) extends AnyVal 5 | case class Ticker(value: String) extends AnyVal 6 | case class BboUpdated(ticker: Ticker, bid: Bid, offer: Offer) -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/thunk.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | import scala.language.{higherKinds, implicitConversions, postfixOps} 4 | import scalaz.{-\/, Free, Functor, \/, \/-} 5 | 6 | case class LimitMs(value: Long) extends AnyVal 7 | 8 | sealed trait Thunk[A] 9 | case class Timed[A]( 10 | whenActive: () => A, 11 | whenExpired: () => A, 12 | limit: LimitMs) extends Thunk[A] 13 | case class StartProcessing[A]( 14 | whenActive: BboUpdated => A, 15 | whenExpired: BboUpdated => A, 16 | limit: LimitMs) extends Thunk[A] 17 | case class TradingDecision[A]( 18 | makeDecision: TradingStrategy => A) extends Thunk[A] 19 | 20 | object Thunk { 21 | implicit val functor: Functor[Thunk] = new Functor[Thunk] { 22 | def map[A, B](t: Thunk[A])(f: (A) => B): Thunk[B] = t match { 23 | case Timed(whenActive, whenExpired, limit) => 24 | Timed(() => f(whenActive()), () => f(whenExpired()), limit) 25 | case StartProcessing(whenActive, whenExpired, limit) => 26 | StartProcessing(c => f(whenActive(c)), c => f(whenExpired(c)), limit) 27 | case TradingDecision(makeDecision) => TradingDecision( 28 | (makeDecision.apply _).andThen(f)) 29 | } 30 | } 31 | 32 | def timed[L, R]( 33 | f: () => R, 34 | exp: () => L, 35 | limit: LimitMs): Free[Thunk, L \/ R] = Free.liftF( 36 | Timed(() => \/-(f()), () => -\/(exp()), limit)) 37 | def startProcessing[L, R]( 38 | f: BboUpdated => R, 39 | exp: BboUpdated => L, 40 | limit: LimitMs): Free[Thunk, L \/ R] = 41 | Free.liftF(StartProcessing(f.andThen(\/-(_)), exp.andThen(-\/(_)), limit)) 42 | def tradingDecision[L, R](f: TradingStrategy => R): Free[Thunk, L \/ R] = 43 | Free.liftF(TradingDecision((f.apply _).andThen(\/-(_)))) 44 | } 45 | -------------------------------------------------------------------------------- /chapter7/src/main/scala/highperfscala/free/tradingStrategies.scala: -------------------------------------------------------------------------------- 1 | package highperfscala.free 2 | 3 | import java.util.concurrent.atomic.AtomicLong 4 | import java.util.concurrent.locks.LockSupport 5 | 6 | trait TradingStrategy { 7 | def makeTradingDecision(e: BboUpdated): Option[Either[Bid, Offer]] 8 | } 9 | 10 | class ProductionStrategy(counter: AtomicLong) extends TradingStrategy { 11 | def makeTradingDecision(e: BboUpdated): Option[Either[Bid, Offer]] = { 12 | val c = counter.getAndIncrement() 13 | c % 1000 == 0 match { 14 | case true => 15 | Thread.sleep(10) 16 | Some(Right(e.offer)) 17 | case false => 18 | LockSupport.parkNanos(20000) // 0.02ms 19 | c % 3 == 0 match { 20 | case true => Some(Left(e.bid)) 21 | case false => None 22 | } 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /chapter7/target/streams/$global/clean/$global/streams/out: -------------------------------------------------------------------------------- 1 | [debug] no default cache defined: set to /Users/vincent/.ivy2/cache 2 | -------------------------------------------------------------------------------- /chapter7/target/streams/$global/ivyConfiguration/$global/streams/out: -------------------------------------------------------------------------------- 1 | [debug] Other repositories: 2 | [debug] Default repositories: 3 | [debug] Using inline dependencies specified in Scala. 4 | -------------------------------------------------------------------------------- /chapter7/target/streams/$global/ivySbt/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter7/target/streams/$global/ivySbt/$global/streams/out -------------------------------------------------------------------------------- /chapter7/target/streams/$global/projectDescriptors/$global/streams/out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Scala-High-Performance-Programming/2d2e339045d1606dc5dc081feb8abe7c5f5dbbf0/chapter7/target/streams/$global/projectDescriptors/$global/streams/out -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.11 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.6") 2 | --------------------------------------------------------------------------------