From bb5a23ff5485704fd5cd6aa92fda568ca6673fd1 Mon Sep 17 00:00:00 2001 From: Manoel Campos Date: Tue, 20 Oct 2026 17:33:22 -0300 Subject: [PATCH] Add benchmark to compare notifying listeners stored in lists and arrays Measures the time and memory to notify listeners stored in the immutable lists created by the Listeners class (cloudsimplus/cloudsimplus#586) and in plain arrays. Run with -prof gc to see if the for-each loop creates an iterator. Co-Authored-By: Claude Opus 5.5 --- .../ListenersNotificationBenchmark.java | 166 ++++++++++++++++++ .../cloudsimplus/listeners/package-info.java | 29 +++ 2 files changed, 195 insertions(+) create mode 100644 src/main/java/org/cloudsimplus/listeners/ListenersNotificationBenchmark.java create mode 100644 src/main/java/org/cloudsimplus/listeners/package-info.java diff --git a/src/main/java/org/cloudsimplus/listeners/ListenersNotificationBenchmark.java b/src/main/java/org/cloudsimplus/listeners/ListenersNotificationBenchmark.java new file mode 100644 index 0000000..ce662a4 --- /dev/null +++ b/src/main/java/org/cloudsimplus/listeners/ListenersNotificationBenchmark.java @@ -0,0 +1,166 @@ +/* + * CloudSim Plus: A modern, highly-extensible and easier-to-use Framework for + * Modeling and Simulation of Cloud Computing Infrastructures and Services. + * http://cloudsimplus.org + * + * Copyright (C) 2015-2021 Universidade da Beira Interior (UBI, Portugal) and + * the Instituto Federal de Educação Ciência e Tecnologia do Tocantins (IFTO, Brazil). + * + * This file is part of CloudSim Plus. + * + * CloudSim Plus is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CloudSim Plus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CloudSim Plus. If not, see . + */ +package org.cloudsimplus.listeners; + +import org.openjdk.jmh.annotations.*; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/// Measures the time and memory to notify listeners stored in: +/// - an immutable [List] created by [Listeners] (as CloudSim Plus does) and +/// - a plain array, which is the alternative being assessed. +/// +/// A for-each loop over a [List] calls [List#iterator()], which creates an object, +/// unless the JIT removes it using escape analysis. A for-each loop over an array creates no object. +/// Run with the GC profiler (`-prof gc`) and check `gc.alloc.rate.norm` (bytes per operation) +/// to see if the iterator is created. +/// +/// The listeners just increment a counter, and all of them get the same [EventInfo], +/// so only the iteration is measured (the framework creates an [EventInfo] for each listener). +/// +/// There are 2 kinds of benchmarks: +/// - `single*`: notifies one list (or array) with [SingleSize#listenersCount] listeners. +/// Since each param value runs in its own fork, the loop only sees one [List] class +/// (`List.of()` lists with 0 or 3+ elements are `ListN`, with 1 or 2 elements are `List12`). +/// - `mixed*`: notifies lists with 0 to [MixedSizes#MAX_LISTENERS] listeners in each operation, +/// like a simulation with many VMs that have different numbers of listeners. +/// This way, the loop sees both [List] classes, as it happens in a real simulation. +/// +/// @author Manoel Campos da Silva Filho +/// @see CloudSim Plus issue #586 +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +public class ListenersNotificationBenchmark { + /// The [EventInfo] given to all listeners. + private static final EventInfo EVENT_INFO = EventInfo.of(info -> {}, 0); + + /// Counts how many times listeners were called, so that the JIT can't remove the calls. + @State(Scope.Thread) + public static class Counter { + long calls; + } + + /// Stores a given number of listeners, both in a list and in an array. + @State(Scope.Thread) + public static class SingleSize { + @Param({"0", "1", "2", "3"}) + int listenersCount; + + @SuppressWarnings("NullAway.Init") // initialized in setup() + List> list; + + @SuppressWarnings("NullAway.Init") // initialized in setup() + EventListener[] array; + + @Setup(Level.Trial) + public void setup(final Counter counter) { + list = newList(listenersCount, counter); + array = toArray(list); + } + } + + /// Stores lists (and arrays) with 0 to [#MAX_LISTENERS] listeners. + @State(Scope.Thread) + public static class MixedSizes { + static final int MAX_LISTENERS = 3; + + @SuppressWarnings("NullAway.Init") // initialized in setup() + List>[] lists; + + @SuppressWarnings("NullAway.Init") // initialized in setup() + EventListener[][] arrays; + + @Setup(Level.Trial) + @SuppressWarnings("unchecked") + public void setup(final Counter counter) { + lists = new List[MAX_LISTENERS + 1]; + arrays = new EventListener[MAX_LISTENERS + 1][]; + for (int count = 0; count <= MAX_LISTENERS; count++) { + lists[count] = newList(count, counter); + arrays[count] = toArray(lists[count]); + } + } + } + + @Benchmark + public long singleList(final SingleSize state, final Counter counter) { + notifyListeners(state.list); + return counter.calls; + } + + @Benchmark + public long singleArray(final SingleSize state, final Counter counter) { + notifyListeners(state.array); + return counter.calls; + } + + @Benchmark + public long mixedList(final MixedSizes state, final Counter counter) { + for (final var list : state.lists) { + notifyListeners(list); + } + + return counter.calls; + } + + @Benchmark + public long mixedArray(final MixedSizes state, final Counter counter) { + for (final var array : state.arrays) { + notifyListeners(array); + } + + return counter.calls; + } + + private static void notifyListeners(final List> listeners) { + for (final var listener : listeners) { + listener.update(EVENT_INFO); + } + } + + private static void notifyListeners(final EventListener[] listeners) { + for (final var listener : listeners) { + listener.update(EVENT_INFO); + } + } + + /// Creates a list with the given number of listeners, using [Listeners#add(List, Object)]. + /// Each listener is a different lambda instance, since it captures its own id. + private static List> newList(final int listenersCount, final Counter counter) { + List> list = List.of(); + for (int i = 0; i < listenersCount; i++) { + final int id = i + 1; + list = Listeners.add(list, info -> counter.calls += id); + } + + return list; + } + + @SuppressWarnings("unchecked") + private static EventListener[] toArray(final List> list) { + return Arrays.copyOf(list.toArray(), list.size(), EventListener[].class); + } +} diff --git a/src/main/java/org/cloudsimplus/listeners/package-info.java b/src/main/java/org/cloudsimplus/listeners/package-info.java new file mode 100644 index 0000000..b39cc94 --- /dev/null +++ b/src/main/java/org/cloudsimplus/listeners/package-info.java @@ -0,0 +1,29 @@ +/* + * CloudSim Plus: A modern, highly-extensible and easier-to-use Framework for + * Modeling and Simulation of Cloud Computing Infrastructures and Services. + * http://cloudsimplus.org + * + * Copyright (C) 2015-2021 Universidade da Beira Interior (UBI, Portugal) and + * the Instituto Federal de Educação Ciência e Tecnologia do Tocantins (IFTO, Brazil). + * + * This file is part of CloudSim Plus. + * + * CloudSim Plus is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CloudSim Plus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CloudSim Plus. If not, see . + */ + +/// Benchmarks for the [org.cloudsimplus.listeners] package. +@NullMarked +package org.cloudsimplus.listeners; + +import org.jspecify.annotations.NullMarked;