Skip to content

chore: optimize lazy val with power of two. - #22428

Merged
sjrd merged 1 commit into
scala:mainfrom
He-Pin:lazyVal
Feb 27, 2026
Merged

chore: optimize lazy val with power of two.#22428
sjrd merged 1 commit into
scala:mainfrom
He-Pin:lazyVal

Conversation

@He-Pin

@He-Pin He-Pin commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

Motivation:
Use & instead of %, which is faster.

private val base: Int = {
val processors = java.lang.Runtime.getRuntime.nn.availableProcessors()
8 * processors * processors
val rawSize = 8 * processors * processors

@He-Pin He-Pin Jan 21, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why it's 8 here, Some CPU has 384 cores now, maybe 2 * 2 is better than 8.

The current implementation may use a little more memory than it was, but as many server just has 64 cores, which is the same.

@szymon-rd szymon-rd Apr 26, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see from blame that it is here for some time. But availableProcessors gives the number of logical cores, so no relation to CPU count, just the actual cores. And I don't think this code is actually used in the new lazy vals impl?


if (id < 0) id += base
monitors(id)
monitors((java.lang.System.identityHashCode(obj) + fieldId) & mask)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use & instead of %

Comment thread library/src/scala/runtime/LazyVals.scala
@He-Pin

He-Pin commented Jan 22, 2025

Copy link
Copy Markdown
Contributor Author
image

jmh-result.json

with https://jmh.morethan.io

[info] Benchmark                          (processors)   Mode  Cnt           Score          Error  Units
[info] ModuloBenchmark.newPowerOf2Method            16  thrpt   10  1169017875.314 Âą 25717906.652  ops/s
[info] ModuloBenchmark.newPowerOf2Method            32  thrpt   10  1169948109.834 Âą 13735667.926  ops/s
[info] ModuloBenchmark.newPowerOf2Method            64  thrpt   10  1169506488.098 Âą 14091486.582  ops/s
[info] ModuloBenchmark.newPowerOf2Method            96  thrpt   10  1171280212.547 Âą  7645883.638  ops/s
[info] ModuloBenchmark.newPowerOf2Method           128  thrpt   10  1169116163.009 Âą 15487127.019  ops/s
[info] ModuloBenchmark.oldModuloMethod              16  thrpt   10  1020610301.567 Âą 14270157.632  ops/s
[info] ModuloBenchmark.oldModuloMethod              32  thrpt   10  1016082948.598 Âą 11667961.439  ops/s
[info] ModuloBenchmark.oldModuloMethod              64  thrpt   10  1024484355.834 Âą 11580908.271  ops/s
[info] ModuloBenchmark.oldModuloMethod              96  thrpt   10  1016482302.606 Âą 12229045.684  ops/s
[info] ModuloBenchmark.oldModuloMethod             128  thrpt   10  1016325470.534 Âą 10125272.051  ops/s
[info] ModuloBenchmark.newPowerOf2Method            16   avgt   10           0.856 Âą        0.016  ns/op
[info] ModuloBenchmark.newPowerOf2Method            32   avgt   10           0.844 Âą        0.011  ns/op
[info] ModuloBenchmark.newPowerOf2Method            64   avgt   10           0.856 Âą        0.011  ns/op
[info] ModuloBenchmark.newPowerOf2Method            96   avgt   10           0.846 Âą        0.005  ns/op
[info] ModuloBenchmark.newPowerOf2Method           128   avgt   10           0.855 Âą        0.012  ns/op
[info] ModuloBenchmark.oldModuloMethod              16   avgt   10           0.984 Âą        0.016  ns/op
[info] ModuloBenchmark.oldModuloMethod              32   avgt   10           0.982 Âą        0.009  ns/op
[info] ModuloBenchmark.oldModuloMethod              64   avgt   10           0.984 Âą        0.011  ns/op
[info] ModuloBenchmark.oldModuloMethod              96   avgt   10           0.977 Âą        0.009  ns/op
[info] ModuloBenchmark.oldModuloMethod             128   avgt   10           0.982 Âą        0.010  ns/op

with

package benchmark

import org.openjdk.jmh.annotations.*
import java.util.concurrent.TimeUnit
import scala.util.Random

@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))  // 吞吐量æĻĄåž
@OutputTimeUnit(TimeUnit.SECONDS)        // æŊį§’
@Warmup(iterations = 10, time = 1)
@Measurement(iterations = 10, time = 1)
@Fork(1)
class ModuloBenchmark:
  @Param(Array("16", "32", "64", "96", "128"))
  var processors: Int = _

  private var hashCodes: Array[Int] = _
  private var fieldIds: Array[Int] = _
  private var oldBase: Int = _
  private var newBase: Int = _
  private var mask: Int = _

  private val testSize = 1_000_000 // 100äļ‡äļŠæ ·æœŽ

  @Setup(Level.Trial)
  def setup(): Unit =
    oldBase = 8 * processors * processors

    val rawSize = 8 * processors * processors
    newBase = 1 << (32 - Integer.numberOfLeadingZeros(rawSize - 1))
    mask = newBase - 1

    val rng = new Random(42)

    hashCodes = new Array[Int](testSize)
    fieldIds = new Array[Int](testSize)

    var i = 0
    while i < testSize do
      hashCodes(i) = rng.nextInt() | (rng.nextInt() << 16)
      fieldIds(i) = rng.nextInt(10000)
      i += 1

    println(s"""
               |Setting up test with:
               |  Processors: $processors
               |  Old base: $oldBase
               |  New base: $newBase
               |  Mask: $mask
               |""".stripMargin)

  @Benchmark
  def oldModuloMethod(): Unit =
    var i = 0
    while i < testSize do
      val hashCode = hashCodes(i)
      val fieldId = fieldIds(i)
      (hashCode + fieldId) % oldBase
      i += 1

  @Benchmark
  def newPowerOf2Method(): Unit =
    var i = 0
    while i < testSize do
      val hashCode = hashCodes(i)
      val fieldId = fieldIds(i)
      (hashCode + fieldId) & mask
      i += 1

@He-Pin

He-Pin commented Apr 26, 2025

Copy link
Copy Markdown
Contributor Author

@szymon-rd friendly ping, would you like to take a look at this, thanks

@szymon-rd

szymon-rd commented Apr 26, 2025

Copy link
Copy Markdown
Contributor

I believe the new lazy vals don't use those monitors, the implementation I did worked on a CountDownLatch per val. Correct me if I am wrong though.

@He-Pin
He-Pin force-pushed the lazyVal branch 2 times, most recently from 2c50caf to 19faf61 Compare June 19, 2025 09:31
@He-Pin
He-Pin requested a review from a team as a code owner September 17, 2025 06:23
@Gedochao
Gedochao requested review from jchyb, lrytz and sjrd February 12, 2026 16:42
@sjrd

sjrd commented Feb 13, 2026

Copy link
Copy Markdown
Member

Needs a rebase to get the new CI setup.

Copilot AI review requested due to automatic review settings February 27, 2026 07:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the lazy val implementation by using bitwise AND instead of modulo for monitor selection, improving performance at the cost of slightly increased memory usage.

Changes:

  • Modified base calculation to round up to the next power of 2
  • Introduced mask variable for bitwise AND operations
  • Replaced modulo operation with bitwise AND in getMonitor

ðŸ’Ą Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread library/src/scala/runtime/LazyVals.scala
Comment thread library/src/scala/runtime/LazyVals.scala
@sjrd
sjrd enabled auto-merge (squash) February 27, 2026 09:21
@sjrd
sjrd merged commit 965809d into scala:main Feb 27, 2026
70 of 71 checks passed
@He-Pin
He-Pin deleted the lazyVal branch February 27, 2026 10:53
tgodzik added a commit to scala/scala3-lts that referenced this pull request Mar 18, 2026
Motivation:
Use `&` instead of `%`, which is faster.
[Cherry-picked 965809d][modified]
@WojciechMazur WojciechMazur added this to the 3.8.4 milestone Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants