diff --git a/benchmarks/src/main/scala/cats/effect/benchmarks/ArrayStackBenchmark.scala b/benchmarks/src/main/scala/cats/effect/benchmarks/ArrayStackBenchmark.scala new file mode 100644 index 0000000000..d19c3e8445 --- /dev/null +++ b/benchmarks/src/main/scala/cats/effect/benchmarks/ArrayStackBenchmark.scala @@ -0,0 +1,58 @@ +/* + * Copyright 2020-2024 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect.benchmarks + +import cats.effect.ArrayStack + +import org.openjdk.jmh.annotations._ + +import java.util.concurrent.TimeUnit + +/** + * To do comparative benchmarks between versions: + * + * benchmarks/run-benchmark ArrayStackBenchmark + * + * This will generate results in `benchmarks/results`. + * + * Or to run the benchmark from within sbt: + * + * Jmh / run -i 10 -wi 10 -f 2 -t 1 cats.effect.benchmarks.ArrayStackBenchmark + * + * Which means "10 iterations", "10 warm-up iterations", "2 forks", "1 thread". Please note that + * benchmarks should be usually executed at least in 10 iterations (as a rule of thumb), but + * more is better. + */ +@State(Scope.Thread) +@BenchmarkMode(Array(Mode.Throughput)) +@OutputTimeUnit(TimeUnit.SECONDS) +class ArrayStackBenchmark { + + @Param(Array("1000000")) + var size: Int = _ + + @Benchmark + def push(): Unit = { + val stack: ArrayStack[Integer] = ArrayStack[Integer](size) + + var i = 0 + while (i < size) { + stack.push(i) + i += 1 + } + } +} diff --git a/core/jvm-native/src/main/scala/cats/effect/ArrayStack.scala b/core/jvm-native/src/main/scala/cats/effect/ArrayStack.scala index ae5d5f7e25..6ea74dcbdf 100644 --- a/core/jvm-native/src/main/scala/cats/effect/ArrayStack.scala +++ b/core/jvm-native/src/main/scala/cats/effect/ArrayStack.scala @@ -16,9 +16,10 @@ package cats.effect +import PlatformStatics.VM_MaxArraySize import Platform.static -private final class ArrayStack[A <: AnyRef]( +private[effect] final class ArrayStack[A <: AnyRef]( private[this] var buffer: Array[AnyRef], private[this] var index: Int) { @@ -72,7 +73,17 @@ private final class ArrayStack[A <: AnyRef]( private[this] def checkAndGrow(): Unit = if (index >= buffer.length) { val len = buffer.length - val buffer2 = new Array[AnyRef](len * 2) + val targetLen = len * 2 + + val resizeLen = + if (targetLen < 0) + throw new Exception(s"Overflow while resizing array. Request length: $targetLen") + else if (len > VM_MaxArraySize / 2) + VM_MaxArraySize + else + targetLen + + val buffer2 = new Array[AnyRef](resizeLen) System.arraycopy(buffer, 0, buffer2, 0, len) buffer = buffer2 } diff --git a/core/jvm-native/src/main/scala/cats/effect/CallbackStack.scala b/core/jvm-native/src/main/scala/cats/effect/CallbackStack.scala index bad66991eb..91ce2520e9 100644 --- a/core/jvm-native/src/main/scala/cats/effect/CallbackStack.scala +++ b/core/jvm-native/src/main/scala/cats/effect/CallbackStack.scala @@ -20,8 +20,7 @@ import scala.annotation.tailrec import java.util.concurrent.atomic.{AtomicBoolean, AtomicReference} -import CallbackStack.Handle -import CallbackStack.Node +import CallbackStack.{Handle, Node} import Platform.static private final class CallbackStack[A](private[this] var callback: A => Unit) diff --git a/core/jvm-native/src/main/scala/cats/effect/PlatformStatics.scala b/core/jvm-native/src/main/scala/cats/effect/PlatformStatics.scala new file mode 100644 index 0000000000..a156ae08dc --- /dev/null +++ b/core/jvm-native/src/main/scala/cats/effect/PlatformStatics.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2020-2024 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect + +// copy from scala.runtime.PStatics +private[effect] object PlatformStatics { + // `Int.MaxValue - 8` traditional soft limit to maximize compatibility with diverse JVMs + // See https://stackoverflow.com/a/8381338 for example + final val VM_MaxArraySize = 2147483639 +}