jamesfredley commented on code in PR #2381: URL: https://github.com/apache/groovy/pull/2381#discussion_r2810132053
########## subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/ClosureBench.groovy: ########## @@ -0,0 +1,309 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.groovy.perf + +import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.infra.Blackhole + +import java.util.concurrent.TimeUnit + +/** + * Tests closure performance including creation, reuse, multi-parameter + * invocation, variable capture, delegation, nesting, method references, + * currying, composition, spread operator, trampoline recursion, and + * collection operations (each/collect/findAll/inject). + */ +@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) +@Fork(3) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +class ClosureBench { + static final int ITERATIONS = 1_000_000 + + String instanceProperty = "instance" + + /** + * Benchmark: Simple closure creation and invocation + */ + @Benchmark + void benchmarkSimpleClosureCreation(Blackhole bh) { + for (int i = 0; i < ITERATIONS; i++) { + Closure c = { it * 2 } + bh.consume(c(i)) + } + } + + /** + * Benchmark: Reuse same closure (no creation overhead) + */ + @Benchmark + void benchmarkClosureReuse(Blackhole bh) { + Closure c = { it * 2 } + int sum = 0 + for (int i = 0; i < ITERATIONS; i++) { + sum += c(i) + } + bh.consume(sum) + } + + /** + * Benchmark: Closure with multiple parameters + */ + @Benchmark + void benchmarkClosureMultiParams(Blackhole bh) { + Closure c = { a, b, c -> a + b + c } + int sum = 0 + for (int i = 0; i < ITERATIONS; i++) { + sum += c(i, i + 1, i + 2) + } Review Comment: Fixed — renamed the closure parameter from `c` to `x` to avoid shadowing. ########## subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/OperatorBench.groovy: ########## @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.groovy.perf + +import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.infra.Blackhole + +import java.util.concurrent.TimeUnit + +/** + * Tests the performance of Groovy operator overloading. In Groovy every + * operator (+, -, *, /, [], <<, ==, <=>) compiles to a method call + * (plus, minus, multiply, div, getAt, leftShift, equals, compareTo) + * dispatched through invokedynamic. + */ +@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) +@Fork(3) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +class OperatorBench { + static final int ITERATIONS = 1_000_000 + + /** + * Integer addition — dispatches to Integer.plus(Integer). + */ + @Benchmark + void integerPlus(Blackhole bh) { + int sum = 0 + for (int i = 0; i < ITERATIONS; i++) { + sum = sum + i + } + bh.consume(sum) + } + + /** + * Integer multiplication — dispatches to Integer.multiply(Integer). + */ + @Benchmark + void integerMultiply(Blackhole bh) { + int product = 1 + for (int i = 1; i < ITERATIONS; i++) { + product = (i % 100) * (i % 50) Review Comment: Fixed — updated the javadoc to explain that the modulo prevents overflow while still exercising the multiply operator each iteration. ########## subprojects/performance/src/jmh/groovy/org/apache/groovy/perf/LoopsBench.groovy: ########## @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.groovy.perf + +import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.infra.Blackhole + +import java.util.concurrent.TimeUnit + +/** + * Tests the overhead of repeated closure and method invocation within + * tight loops. Focuses on loop-specific patterns: closure-in-loop vs + * method-in-loop, nested iteration, and minimal vs complex loop bodies. + * + * Collection operation benchmarks (each/collect/findAll/inject on lists) + * are in {@link ClosureBench}. + */ +@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) +@Fork(3) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +class LoopsBench { + static final int LOOP_COUNT = 1_000_000 + + /** + * Loop with [1].each and toString() — exercises closure dispatch + * and virtual method call on each iteration. + */ + @Benchmark + void originalEachToString(Blackhole bh) { + for (int i = 0; i < LOOP_COUNT; i++) { + [1].each { bh.consume(it.toString()) } Review Comment: Intentional — this benchmarks the real-world `[1].each { ... }` pattern as developers actually write it. The allocation is part of what's being measured. The paired `eachIdentity` benchmark uses a pre-allocated list to isolate the iteration overhead, so comparing the two reveals exactly the allocation cost. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
