From c40beae1ff9418372711db818365cb7c21d0bc22 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 May 2026 04:27:37 -0400 Subject: [PATCH] Add benchmark --- build.sbt | 11 ++++ .../sbt/internal/util/HashBenchmark.scala | 60 +++++++++++++++++++ project/plugins.sbt | 1 + .../src/main/scala/sbt/util/Digest.scala | 3 + 4 files changed, 75 insertions(+) create mode 100644 internal/hash-benchmark/src/main/scala/sbt/internal/util/HashBenchmark.scala diff --git a/build.sbt b/build.sbt index 67ad2ae0c..c78323fcf 100644 --- a/build.sbt +++ b/build.sbt @@ -396,6 +396,17 @@ lazy val utilCache = project addSbtCompilerInterface, ) +lazy val hashBenchmark = (project in file("internal") / "hash-benchmark") + .dependsOn(utilControl, utilCache) + .enablePlugins(JmhPlugin) + .settings( + utilCommonSettings, + name := "Hash Benchmark", + Jmh / run / javaOptions ++= Seq("-Xmx1G", "-Dfile.encoding=UTF8"), + mimaSettings, + publish / skip := true, + ) + // Builds on cache to provide caching for filesystem-related operations lazy val utilTracking = (project in file("util-tracking")) .dependsOn(utilCache) diff --git a/internal/hash-benchmark/src/main/scala/sbt/internal/util/HashBenchmark.scala b/internal/hash-benchmark/src/main/scala/sbt/internal/util/HashBenchmark.scala new file mode 100644 index 000000000..ef0d522c0 --- /dev/null +++ b/internal/hash-benchmark/src/main/scala/sbt/internal/util/HashBenchmark.scala @@ -0,0 +1,60 @@ +package sbt.internal.util + +import java.util.concurrent.{ ThreadLocalRandom, TimeUnit } +import net.openhft.hashing.LongHashFunction +import org.openjdk.jmh.annotations.* +import sbt.util.Digest +import sbt.internal.util.hashing.Hashing +import scala.util.hashing.MurmurHash3 + +@State(Scope.Benchmark) +abstract class AbstractHashBenchmark: + def hash(buf: Array[Byte]): String + + val buf: Array[Byte] = new Array[Byte](2048) + ThreadLocalRandom.current().nextBytes(buf) + + @Benchmark + @BenchmarkMode(Array(Mode.AverageTime)) + @OutputTimeUnit(TimeUnit.MICROSECONDS) + def hashByteArray: Unit = + hash(buf) +end AbstractHashBenchmark + +class XXHash64HashBenchmark extends AbstractHashBenchmark: + override def hash(buf: Array[Byte]): String = + val h = Hashing.xxhash64 + val hash = h.hash(buf, 0, buf.size, 0) + java.lang.Long.toHexString(hash) + +class WyHash64HashBenchmark extends AbstractHashBenchmark: + override def hash(buf: Array[Byte]): String = + val h = Hashing.wyhash64 + val hash = h.hash(buf, 0, buf.size, 0) + java.lang.Long.toHexString(hash) + +class FarmHashHashBenchmark extends AbstractHashBenchmark: + override def hash(buf: Array[Byte]): String = + val hash = LongHashFunction.farmNa().hashBytes(buf) + java.lang.Long.toHexString(hash) + +class MurmurHash32HashBenchmark extends AbstractHashBenchmark: + override def hash(buf: Array[Byte]): String = + val lo = MurmurHash3.bytesHash(buf, 0x85ebca6b) + val hash = lo.toLong & 0xffffffffL + java.lang.Long.toHexString(hash) + +class MurmurHash64HashBenchmark extends AbstractHashBenchmark: + override def hash(buf: Array[Byte]): String = + val hi = MurmurHash3.bytesHash(buf, 0x9747b28c) + val lo = MurmurHash3.bytesHash(buf, 0x85ebca6b) + val hash = (hi.toLong << 32) | (lo.toLong & 0xffffffffL) + java.lang.Long.toHexString(hash) + +class Md5HashBenchmark extends AbstractHashBenchmark: + override def hash(buf: Array[Byte]): String = + Digest.md5Hash(buf).toString + +class Sha256HashBenchmark extends AbstractHashBenchmark: + override def hash(buf: Array[Byte]): String = + Digest.sha256Hash(buf).toString diff --git a/project/plugins.sbt b/project/plugins.sbt index a6a70315a..43bfde102 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -13,5 +13,6 @@ addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.4.0") addDependencyTreePlugin addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.5") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.7") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.8") // libraryDependencies += "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value diff --git a/util-cache/src/main/scala/sbt/util/Digest.scala b/util-cache/src/main/scala/sbt/util/Digest.scala index 2e95324f1..8e059b8ea 100644 --- a/util-cache/src/main/scala/sbt/util/Digest.scala +++ b/util-cache/src/main/scala/sbt/util/Digest.scala @@ -67,6 +67,9 @@ object Digest: def sha256Hash(digests: Digest*): Digest = sha256Hash(digests.toSeq.map(_.toBytes).flatten.toArray[Byte]) + private[sbt] def md5Hash(bytes: Array[Byte]): Digest = + apply(Md5, hashBytes(Md5, bytes), bytes.length) + // first check the file size, then the hash def sameDigest(path: Path, digest: Digest): Boolean = if Files.size(path) != digest.sizeBytes then false