diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 11dc57a..d10d16a 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -17,14 +17,5 @@ jobs: distribution: 'corretto' java-version: 25 cache: 'maven' - - name: Install CloudSim Plus before cloudsimplus#595 - # The benchmarks use the CloudSim Plus API before cloudsimplus#595 (a List per Host), - # which isn't published. So it's built from the commit right before that PR. - # Remove this step (and -nsu below) when the benchmarks use the API after #595. - run: | - git clone --quiet --filter=blob:none https://github.com/cloudsimplus/cloudsimplus.git "$RUNNER_TEMP/cloudsimplus" - git -C "$RUNNER_TEMP/cloudsimplus" checkout --quiet 9bb9de25c - mvn -B -q -f "$RUNNER_TEMP/cloudsimplus/pom.xml" install -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip - name: Maven Build - # -nsu (no snapshot updates) avoids replacing the CloudSim Plus installed above by the published 9.0.0-SNAPSHOT. - run: mvn -B -nsu package + run: mvn -B package diff --git a/README.md b/README.md index 5f232e8..e0a10e7 100755 --- a/README.md +++ b/README.md @@ -21,3 +21,32 @@ on Windows ```bash mvnw.cmd clean install ``` + +## Results of `ProcessorBenchmark` (cloudsimplus#595) + +`ProcessorBenchmark` measures the memory footprint and simulation time reduction from [cloudsimplus#595](https://github.com/cloudsimplus/cloudsimplus/pull/595), which replaced the `List` of Hosts (one object per CPU core) by a single `Processor` that just counts the cores. + +- **Before:** CloudSim Plus built from commit `9bb9de25c`, running the first version of the benchmark (using the `List` API). +- **After:** CloudSim Plus with #595 (`79eb0fb09`). +- JDK 25.0.4, default GC, `-Xmx4g`. Each value is the median of 5 rounds. The simulation time is measured after 2 warmup runs. + +### Heap retained by each machine (bytes) + +| Cores | Host before | Host after | Reduction | VM before | VM after | Reduction | +| ----: | ----------: | ---------: | --------: | --------: | -------: | --------: | +| 1 | 940.6 | 812.6 | 14% | 883.3 | 795.3 | 10% | +| 8 | 1,807.1 | 808.1 | 55% | 891.9 | 795.5 | 11% | +| 32 | 4,804.7 | 821.4 | 83% | 883.6 | 795.6 | 10% | +| 128 | 16,722.2 | 811.2 | 95% | 883.1 | 804.5 | 9% | + +Before #595, a Host grew about 123 bytes per core. Now its size doesn't depend on the number of cores. VMs were already using a single `Processor` before #595, so their size didn't depend on the number of cores either. + +### Whole simulation + +Scenario: 1,000 Hosts with 32 cores, 4,000 VMs with 8 cores and 8,000 Cloudlets with 4 cores. + +| Measure | Before | After | Reduction | +| ---------------------------- | ------: | ------: | --------: | +| Heap retained before running | 9.6 MB | 5.5 MB | 43% | +| Heap retained after running | 12.2 MB | 8.6 MB | 30% | +| Simulation time | 1.691 s | 0.776 s | 54% | diff --git a/src/main/java/org/cloudsimplus/benchmarks/MemoryFootprintBenchmark.java b/src/main/java/org/cloudsimplus/benchmarks/MemoryFootprintBenchmark.java index 12ed5a9..c6d13b1 100644 --- a/src/main/java/org/cloudsimplus/benchmarks/MemoryFootprintBenchmark.java +++ b/src/main/java/org/cloudsimplus/benchmarks/MemoryFootprintBenchmark.java @@ -25,14 +25,8 @@ import org.cloudsimplus.cloudlets.CloudletSimple; import org.cloudsimplus.hosts.HostSimple; -import org.cloudsimplus.resources.CoreCapacity; import org.cloudsimplus.vms.VmSimple; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.lang.ref.Reference; -import java.util.Arrays; -import java.util.List; import java.util.function.IntFunction; /// Measures the heap retained by each VM, Host and Cloudlet (their memory footprint), @@ -48,21 +42,20 @@ /// /// It's a plain `main` (not JMH), since JMH measures time and allocation rate, /// but here we need the memory that stays in the heap after the objects are created. -/// It creates [#OBJECTS] objects, keeps them in an array and measures the used heap -/// after a GC, before and after creating them. The difference divided by the number of objects -/// is the heap retained by each object. This is repeated [#ROUNDS] times and the median is shown. +/// It creates [#OBJECTS] objects in each of [#ROUNDS] rounds and shows the median +/// heap retained by each object (see [HeapMeter]). /// -/// The results are only accurate with a stop-the-world GC, where [System#gc()] -/// does a full collection, so run it with `-XX:+UseSerialGC`. +/// To measure the footprint reduction from +/// [#595](https://github.com/cloudsimplus/cloudsimplus/pull/595) for different numbers of cores, use [ProcessorBenchmark]. /// /// To compare CloudSim Plus 8.5.1 (before #586 and #587) with the current version, build and run it once for each version: /// /// ```bash /// ./mvnw clean package -Pcloudsimplus-8 # CloudSim Plus 8.5.1 -/// java -XX:+UseSerialGC -cp target/cloudsimplus-benchmarks-*-uber.jar org.cloudsimplus.benchmarks.MemoryFootprintBenchmark 8.5.1 +/// java -cp target/cloudsimplus-benchmarks-*-uber.jar org.cloudsimplus.benchmarks.MemoryFootprintBenchmark 8.5.1 /// /// ./mvnw clean package # current CloudSim Plus version -/// java -XX:+UseSerialGC -cp target/cloudsimplus-benchmarks-*-uber.jar org.cloudsimplus.benchmarks.MemoryFootprintBenchmark current +/// java -cp target/cloudsimplus-benchmarks-*-uber.jar org.cloudsimplus.benchmarks.MemoryFootprintBenchmark current /// ``` /// /// @author Manoel Campos da Silva Filho @@ -74,9 +67,6 @@ public final class MemoryFootprintBenchmark { /// Number of times each measurement is repeated. private static final int ROUNDS = 5; - /// Number of times [System#gc()] is called before reading the used heap, - /// so that objects waiting for finalization or in reference queues are also collected. - private static final int GC_CALLS = 3; private static final double VM_MIPS = 1000; private static final long VM_CORES = 1; @@ -89,8 +79,6 @@ public final class MemoryFootprintBenchmark { private static final long CLOUDLET_LENGTH = 10_000; private static final int CLOUDLET_CORES = 1; - private static final MemoryMXBean MEMORY = ManagementFactory.getMemoryMXBean(); - /// A private constructor to avoid class instantiation. private MemoryFootprintBenchmark(){} @@ -107,13 +95,13 @@ public static void main(final String[] args) { } private static VmSimple newVm(final int index) { - return new VmSimple(new CoreCapacity(VM_CORES, VM_MIPS)); + return ProcessorScenario.newVm(VM_CORES, VM_MIPS); } /// Creates a Host with a single CPU core. /// The Host Processor is included in the measured size. private static HostSimple newHost(final int index) { - return new HostSimple(HOST_RAM, HOST_BW, HOST_STORAGE, new CoreCapacity(HOST_CORE_MIPS)); + return ProcessorScenario.newHost(HOST_RAM, HOST_BW, HOST_STORAGE, 1, HOST_CORE_MIPS); } /// Creates a Cloudlet with the default utilization models. @@ -123,43 +111,8 @@ private static CloudletSimple newCloudlet(final int index) { /// {@return the median heap (in bytes) retained by each object created by a given function} /// @param factory a function that creates an object for a given index - private static double measure(final IntFunction factory) { - final double[] bytesPerObject = new double[ROUNDS]; - for (int round = 0; round < ROUNDS; round++) { - bytesPerObject[round] = measureRound(factory); - } - - return median(bytesPerObject); - } - - /// {@return the heap (in bytes) retained by each object created by a given function in a single round} - /// The array that keeps the objects alive is created before the first reading, - /// so it isn't included in the measured size. - private static double measureRound(final IntFunction factory) { - final Object[] objects = new Object[OBJECTS]; - final long usedBefore = usedHeapAfterGc(); - for (int i = 0; i < OBJECTS; i++) { - objects[i] = factory.apply(i); - } - - final long usedAfter = usedHeapAfterGc(); - Reference.reachabilityFence(objects); - return (usedAfter - usedBefore) / (double) OBJECTS; - } - - private static long usedHeapAfterGc() { - for (int i = 0; i < GC_CALLS; i++) { - System.gc(); - } - - return MEMORY.getHeapMemoryUsage().getUsed(); - } - - private static double median(final double[] values) { - final double[] sorted = values.clone(); - Arrays.sort(sorted); - final int middle = sorted.length / 2; - return sorted.length % 2 == 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle]; + private static double measure(final IntFunction factory) { + return HeapMeter.bytesPerObject(factory, OBJECTS, ROUNDS); } private static void printResult(final String className, final double bytesPerObject) { diff --git a/src/main/java/org/cloudsimplus/benchmarks/ProcessorBenchmark.java b/src/main/java/org/cloudsimplus/benchmarks/ProcessorBenchmark.java index a53fada..84385d4 100644 --- a/src/main/java/org/cloudsimplus/benchmarks/ProcessorBenchmark.java +++ b/src/main/java/org/cloudsimplus/benchmarks/ProcessorBenchmark.java @@ -47,16 +47,17 @@ /// It's a plain `main` (not JMH), since JMH measures time and allocation rate, /// but here we also need the memory that stays in the heap. /// -/// This version uses the CloudSim Plus API before #595, where each Host has a `List`. -/// Run it with CloudSim Plus built from the commit right before #595: +/// This version uses the current CloudSim Plus API, where each machine has a single `Processor`: /// /// ```bash -/// git -C ../cloudsimplus checkout 9bb9de25c -/// (cd ../cloudsimplus && ./mvnw install -DskipTests) /// ./mvnw clean package /// java -Xmx4g -cp target/cloudsimplus-benchmarks-*-uber.jar org.cloudsimplus.benchmarks.ProcessorBenchmark /// ``` /// +/// The numbers before #595 were got by running the first version of this benchmark +/// (which used the `List` API) with CloudSim Plus built from commit 9bb9de25c. +/// Both results are in the README. +/// /// @author Manoel Campos da Silva Filho public final class ProcessorBenchmark { /// Number of times each measurement is repeated. @@ -80,7 +81,7 @@ public final class ProcessorBenchmark { private static final long HOST_STORAGE = 100_000; /// A description of the CloudSim Plus API used to create Hosts and VMs. - private static final String API = "List (before cloudsimplus#595)"; + private static final String API = "single Processor (cloudsimplus#595)"; /// A private constructor to avoid class instantiation. private ProcessorBenchmark(){} diff --git a/src/main/java/org/cloudsimplus/benchmarks/ProcessorScenario.java b/src/main/java/org/cloudsimplus/benchmarks/ProcessorScenario.java index 8b4b473..b66c640 100644 --- a/src/main/java/org/cloudsimplus/benchmarks/ProcessorScenario.java +++ b/src/main/java/org/cloudsimplus/benchmarks/ProcessorScenario.java @@ -32,8 +32,7 @@ import org.cloudsimplus.datacenters.DatacenterSimple; import org.cloudsimplus.hosts.Host; import org.cloudsimplus.hosts.HostSimple; -import org.cloudsimplus.resources.Pe; -import org.cloudsimplus.resources.PeSimple; +import org.cloudsimplus.resources.CoreCapacity; import org.cloudsimplus.schedulers.vm.VmSchedulerTimeShared; import org.cloudsimplus.vms.Vm; import org.cloudsimplus.vms.VmSimple; @@ -45,7 +44,8 @@ /// It's built when the object is created and executed by [#run()]. /// /// Hosts and VMs are created only by [#newHost(long, long, long, long, double)] and [#newVm(long, double)], -/// which use the CloudSim Plus API where a Host has a `List`, with one [Pe] object per CPU core. +/// which use the CloudSim Plus API where each machine has a single +/// [org.cloudsimplus.resources.Processor] that just counts its cores. /// /// Cloudlets have different lengths, so they finish at different times. /// This way, the simulation processes many events that change the number of @@ -134,18 +134,13 @@ private static List createCloudlets() { /// @param cores the number of CPU cores /// @param mips the MIPS capacity of each core static HostSimple newHost(final long ram, final long bw, final long storage, final long cores, final double mips) { - final List peList = new ArrayList<>((int) cores); - for (int i = 0; i < cores; i++) { - peList.add(new PeSimple(mips)); - } - - return new HostSimple(ram, bw, storage, peList); + return new HostSimple(ram, bw, storage, new CoreCapacity(cores, mips)); } /// {@return a new VM} /// @param cores the number of CPU cores /// @param mips the MIPS capacity of each core static VmSimple newVm(final long cores, final double mips) { - return new VmSimple(mips, cores); + return new VmSimple(new CoreCapacity(cores, mips)); } }