From 7e21c5fe2561fa7c3b68adb4e169e61e87c8e873 Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Tue, 4 Jul 2023 16:18:07 +0200 Subject: [PATCH 1/3] Setup benchmark for pieceMap's implementations Add severral implementations for bench-marking --- .../main/scala/benchmarks/PieceMapBench.scala | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 bench/src/main/scala/benchmarks/PieceMapBench.scala diff --git a/bench/src/main/scala/benchmarks/PieceMapBench.scala b/bench/src/main/scala/benchmarks/PieceMapBench.scala new file mode 100644 index 000000000..4c09056ee --- /dev/null +++ b/bench/src/main/scala/benchmarks/PieceMapBench.scala @@ -0,0 +1,138 @@ +package benchmarks + +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit + +import chess.{ Piece, Square } +import chess.format.* +import chess.bitboard.{ Board, FenFixtures } +import org.openjdk.jmh.infra.Blackhole +import chess.bitboard.Bitboard +import chess.Role +import chess.Color + +@State(Scope.Thread) +@BenchmarkMode(Array(Mode.Throughput)) +@OutputTimeUnit(TimeUnit.SECONDS) +@Measurement(iterations = 15, timeUnit = TimeUnit.SECONDS, time = 3) +@Warmup(iterations = 15, timeUnit = TimeUnit.SECONDS, time = 3) +@Fork(3) +@Threads(value = 1) +class PieceMapBench: + + def parseFen(fen: EpdFen): Board = + Fen.read(fen).map(_.board.board).getOrElse(throw RuntimeException("boooo")) + + var bs: List[Board] = _ + + @Setup + def setup() = + bs = for + str <- FenFixtures.fens + board = parseFen(str) + yield board + + def run(f: Board => Map[Square, Piece]) = + bs.map: x => + Blackhole.consumeCPU(10) + f(x) + + @Benchmark + def current = + run(_.pieceMapImpl1) + + @Benchmark + def mapforSquaresViewMap = + run(_.pieceMapImpl2) + + @Benchmark + def flatMap = + run(_.pieceMapImpl3) + + @Benchmark + def byRole = + run(_.pieceMapImpl4) + + @Benchmark + def byRoleWithoutInnerFunction = + run(_.pieceMapImpl5) + + @Benchmark + def byRoleWithMutableMap = + run(_.pieceMapImpl6) + + @Benchmark + def foreachWithMutableMap = + run(_.pieceMapImpl7) + + @Benchmark + def foreachWithMutableMapAndNoGet = + run(_.pieceMapImpl8) + + @Benchmark + def foreachWithMapBuilder = + run(_.pieceMapImpl9) + + @Benchmark + def foreachWithMapBuilderAndNoGet = + run(_.pieceMapImpl10) + +extension (b: Board) + // implementation at 15.4.0 + def pieceMapImpl1: Map[Square, Piece] = + b.occupied.squares.view.map(s => (s, b.pieceAt(s).get)).toMap + + def pieceMapImpl2: Map[Square, Piece] = + b.occupied.map(s => (s, b.pieceAt(s).get)).toMap + + def pieceMapImpl3: Map[Square, Piece] = + b.occupied.flatMap(s => b.pieceAt(s).map(s -> _)).toMap + + def pieceMapImpl4: Map[Square, Piece] = + def roleMap(role: Role, bb: Bitboard): Map[Square, Piece] = + bb.map: s => + (s, Piece(Color.fromWhite(b.byColor.white.contains(s)), role)) + .toMap + + b.byRole.fold(Map.empty[Square, Piece]): (acc, role, rx) => + acc ++ roleMap(role, rx) + + def pieceMapImpl5: Map[Square, Piece] = + b.byRole.fold(Map.empty): (acc, role, rx) => + acc ++ + rx.map: s => + (s, Color.fromWhite(b.byColor.white.contains(s)) - role) + .toMap + + def pieceMapImpl6: Map[Square, Piece] = + val m = collection.mutable.Map.empty[Square, Piece] + b.byRole.fold(m): (acc, role, rx) => + acc.addAll( + rx.map: s => + (s, Color.fromWhite(b.byColor.white.contains(s)) - role) + ) + m.toMap + + def pieceMapImpl7: Map[Square, Piece] = + val m = collection.mutable.Map.empty[Square, Piece] + b.occupied.foreach:s => + m += s -> b.pieceAt(s).get + m.toMap + + def pieceMapImpl8: Map[Square, Piece] = + val m = collection.mutable.Map.empty[Square, Piece] + b.occupied.foreach:s => + m ++= b.pieceAt(s).map(s -> _) + m.toMap + + def pieceMapImpl9: Map[Square, Piece] = + val m = Map.newBuilder[Square, Piece] + b.occupied.foreach:s => + m += s -> b.pieceAt(s).get + m.result + + def pieceMapImpl10: Map[Square, Piece] = + val m = Map.newBuilder[Square, Piece] + b.occupied.foreach:s => + m ++= b.pieceAt(s).map(s -> _) + m.result From dc58814d3436c63f6172d9b8aeff510301d1f93b Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Thu, 6 Jul 2023 15:26:35 +0200 Subject: [PATCH 2/3] Add Niklas's implementation --- .../main/scala/benchmarks/PieceMapBench.scala | 43 +++++++++++++++---- src/main/scala/ByColor.scala | 4 ++ src/main/scala/Role.scala | 16 +++++++ 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/bench/src/main/scala/benchmarks/PieceMapBench.scala b/bench/src/main/scala/benchmarks/PieceMapBench.scala index 4c09056ee..8b7af793f 100644 --- a/bench/src/main/scala/benchmarks/PieceMapBench.scala +++ b/bench/src/main/scala/benchmarks/PieceMapBench.scala @@ -41,31 +41,31 @@ class PieceMapBench: def current = run(_.pieceMapImpl1) - @Benchmark + // @Benchmark def mapforSquaresViewMap = run(_.pieceMapImpl2) - @Benchmark + // @Benchmark def flatMap = run(_.pieceMapImpl3) - @Benchmark + // @Benchmark def byRole = run(_.pieceMapImpl4) - @Benchmark + // @Benchmark def byRoleWithoutInnerFunction = run(_.pieceMapImpl5) - @Benchmark + // @Benchmark def byRoleWithMutableMap = run(_.pieceMapImpl6) - @Benchmark + // @Benchmark def foreachWithMutableMap = run(_.pieceMapImpl7) - @Benchmark + // @Benchmark def foreachWithMutableMapAndNoGet = run(_.pieceMapImpl8) @@ -73,10 +73,18 @@ class PieceMapBench: def foreachWithMapBuilder = run(_.pieceMapImpl9) - @Benchmark + // @Benchmark def foreachWithMapBuilderAndNoGet = run(_.pieceMapImpl10) + @Benchmark + def betterForeach = + run(_.pieceMapImpl11) + + @Benchmark + def betterForeachWithMutableMap = + run(_.pieceMapImpl12) + extension (b: Board) // implementation at 15.4.0 def pieceMapImpl1: Map[Square, Piece] = @@ -136,3 +144,22 @@ extension (b: Board) b.occupied.foreach:s => m ++= b.pieceAt(s).map(s -> _) m.result + + def pieceMapImpl11: Map[Square, Piece] = + val m = Map.newBuilder[Square, Piece] + b.byColor.foreach: (color, c) => + b.byRole.foreach: (role, r) => + val piece = color - role + (c & r).foreach: s => + m += s -> piece + m.result + + def pieceMapImpl12: Map[Square, Piece] = + val m = collection.mutable.Map.newBuilder[Square, Piece] + m.sizeHint(b.occupied.count) + b.byColor.foreach: (color, c) => + b.byRole.foreach: (role, r) => + val piece = color - role + (c & r).foreach: s => + m += s -> piece + m.result.toMap diff --git a/src/main/scala/ByColor.scala b/src/main/scala/ByColor.scala index 947affa6a..03101af4c 100644 --- a/src/main/scala/ByColor.scala +++ b/src/main/scala/ByColor.scala @@ -44,6 +44,10 @@ case class ByColor[A](white: A, black: A): f(white) f(black) + def foreach[U](f: (Color, A) => U): Unit = + f(White, white) + f(Black, black) + def forall(pred: A => Boolean) = pred(white) && pred(black) def exists(pred: A => Boolean) = pred(white) || pred(black) diff --git a/src/main/scala/Role.scala b/src/main/scala/Role.scala index 0f96ee3c9..f648cba94 100644 --- a/src/main/scala/Role.scala +++ b/src/main/scala/Role.scala @@ -96,6 +96,22 @@ case class ByRole[A](pawn: A, knight: A, bishop: A, rook: A, queen: A, king: A): def fold[B](z: B)(f: (B, Role, A) => B): B = f(f(f(f(f(f(z, Pawn, pawn), Knight, knight), Bishop, bishop), Rook, rook), Queen, queen), King, king) + def foreach[U](f: A => U): Unit = + f(pawn) + f(knight) + f(bishop) + f(rook) + f(queen) + f(king) + + def foreach[U](f: (Role, A) => U): Unit = + f(Pawn, pawn) + f(Knight, knight) + f(Bishop, bishop) + f(Rook, rook) + f(Queen, queen) + f(King, king) + def findRole(f: A => Boolean): Option[Role] = if f(pawn) then Some(Pawn) else if f(knight) then Some(Knight) From b3ba98cbd0e8a23a75b11a6a7fd490b2b7e9285b Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Thu, 6 Jul 2023 17:14:28 +0200 Subject: [PATCH 3/3] Change pieceMap impl & delete benchmark file --- .../main/scala/benchmarks/PieceMapBench.scala | 165 ------------------ src/main/scala/bitboard/Board.scala | 10 +- 2 files changed, 8 insertions(+), 167 deletions(-) delete mode 100644 bench/src/main/scala/benchmarks/PieceMapBench.scala diff --git a/bench/src/main/scala/benchmarks/PieceMapBench.scala b/bench/src/main/scala/benchmarks/PieceMapBench.scala deleted file mode 100644 index 8b7af793f..000000000 --- a/bench/src/main/scala/benchmarks/PieceMapBench.scala +++ /dev/null @@ -1,165 +0,0 @@ -package benchmarks - -import org.openjdk.jmh.annotations.* -import java.util.concurrent.TimeUnit - -import chess.{ Piece, Square } -import chess.format.* -import chess.bitboard.{ Board, FenFixtures } -import org.openjdk.jmh.infra.Blackhole -import chess.bitboard.Bitboard -import chess.Role -import chess.Color - -@State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) -@OutputTimeUnit(TimeUnit.SECONDS) -@Measurement(iterations = 15, timeUnit = TimeUnit.SECONDS, time = 3) -@Warmup(iterations = 15, timeUnit = TimeUnit.SECONDS, time = 3) -@Fork(3) -@Threads(value = 1) -class PieceMapBench: - - def parseFen(fen: EpdFen): Board = - Fen.read(fen).map(_.board.board).getOrElse(throw RuntimeException("boooo")) - - var bs: List[Board] = _ - - @Setup - def setup() = - bs = for - str <- FenFixtures.fens - board = parseFen(str) - yield board - - def run(f: Board => Map[Square, Piece]) = - bs.map: x => - Blackhole.consumeCPU(10) - f(x) - - @Benchmark - def current = - run(_.pieceMapImpl1) - - // @Benchmark - def mapforSquaresViewMap = - run(_.pieceMapImpl2) - - // @Benchmark - def flatMap = - run(_.pieceMapImpl3) - - // @Benchmark - def byRole = - run(_.pieceMapImpl4) - - // @Benchmark - def byRoleWithoutInnerFunction = - run(_.pieceMapImpl5) - - // @Benchmark - def byRoleWithMutableMap = - run(_.pieceMapImpl6) - - // @Benchmark - def foreachWithMutableMap = - run(_.pieceMapImpl7) - - // @Benchmark - def foreachWithMutableMapAndNoGet = - run(_.pieceMapImpl8) - - @Benchmark - def foreachWithMapBuilder = - run(_.pieceMapImpl9) - - // @Benchmark - def foreachWithMapBuilderAndNoGet = - run(_.pieceMapImpl10) - - @Benchmark - def betterForeach = - run(_.pieceMapImpl11) - - @Benchmark - def betterForeachWithMutableMap = - run(_.pieceMapImpl12) - -extension (b: Board) - // implementation at 15.4.0 - def pieceMapImpl1: Map[Square, Piece] = - b.occupied.squares.view.map(s => (s, b.pieceAt(s).get)).toMap - - def pieceMapImpl2: Map[Square, Piece] = - b.occupied.map(s => (s, b.pieceAt(s).get)).toMap - - def pieceMapImpl3: Map[Square, Piece] = - b.occupied.flatMap(s => b.pieceAt(s).map(s -> _)).toMap - - def pieceMapImpl4: Map[Square, Piece] = - def roleMap(role: Role, bb: Bitboard): Map[Square, Piece] = - bb.map: s => - (s, Piece(Color.fromWhite(b.byColor.white.contains(s)), role)) - .toMap - - b.byRole.fold(Map.empty[Square, Piece]): (acc, role, rx) => - acc ++ roleMap(role, rx) - - def pieceMapImpl5: Map[Square, Piece] = - b.byRole.fold(Map.empty): (acc, role, rx) => - acc ++ - rx.map: s => - (s, Color.fromWhite(b.byColor.white.contains(s)) - role) - .toMap - - def pieceMapImpl6: Map[Square, Piece] = - val m = collection.mutable.Map.empty[Square, Piece] - b.byRole.fold(m): (acc, role, rx) => - acc.addAll( - rx.map: s => - (s, Color.fromWhite(b.byColor.white.contains(s)) - role) - ) - m.toMap - - def pieceMapImpl7: Map[Square, Piece] = - val m = collection.mutable.Map.empty[Square, Piece] - b.occupied.foreach:s => - m += s -> b.pieceAt(s).get - m.toMap - - def pieceMapImpl8: Map[Square, Piece] = - val m = collection.mutable.Map.empty[Square, Piece] - b.occupied.foreach:s => - m ++= b.pieceAt(s).map(s -> _) - m.toMap - - def pieceMapImpl9: Map[Square, Piece] = - val m = Map.newBuilder[Square, Piece] - b.occupied.foreach:s => - m += s -> b.pieceAt(s).get - m.result - - def pieceMapImpl10: Map[Square, Piece] = - val m = Map.newBuilder[Square, Piece] - b.occupied.foreach:s => - m ++= b.pieceAt(s).map(s -> _) - m.result - - def pieceMapImpl11: Map[Square, Piece] = - val m = Map.newBuilder[Square, Piece] - b.byColor.foreach: (color, c) => - b.byRole.foreach: (role, r) => - val piece = color - role - (c & r).foreach: s => - m += s -> piece - m.result - - def pieceMapImpl12: Map[Square, Piece] = - val m = collection.mutable.Map.newBuilder[Square, Piece] - m.sizeHint(b.occupied.count) - b.byColor.foreach: (color, c) => - b.byRole.foreach: (role, r) => - val piece = color - role - (c & r).foreach: s => - m += s -> piece - m.result.toMap diff --git a/src/main/scala/bitboard/Board.scala b/src/main/scala/bitboard/Board.scala index 52a4e977d..94ff17f4e 100644 --- a/src/main/scala/bitboard/Board.scala +++ b/src/main/scala/bitboard/Board.scala @@ -145,9 +145,15 @@ case class Board( inline def isOccupied(inline p: Piece) = piece(p).nonEmpty - // TODO remove unsafe get + // benchmarked: https://github.com/lichess-org/scalachess/pull/438 lazy val pieceMap: Map[Square, Piece] = - occupied.squares.view.map(s => (s, pieceAt(s).get)).toMap + val m = Map.newBuilder[Square, Piece] + byColor.foreach: (color, c) => + byRole.foreach: (role, r) => + val piece = color - role + (c & r).foreach: s => + m += s -> piece + m.result def piecesOf(c: Color): Map[Square, Piece] = pieceMap.filter((_, p) => p.color == c)