Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pe> 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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pe>` 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<Pe>` 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% |
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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(){}

Expand All @@ -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.
Expand All @@ -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<Object> 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<Object> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pe>`.
/// 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<Pe>` 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.
Expand All @@ -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<Pe> (before cloudsimplus#595)";
private static final String API = "single Processor (cloudsimplus#595)";

/// A private constructor to avoid class instantiation.
private ProcessorBenchmark(){}
Expand Down
15 changes: 5 additions & 10 deletions src/main/java/org/cloudsimplus/benchmarks/ProcessorScenario.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Pe>`, 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
Expand Down Expand Up @@ -134,18 +134,13 @@ private static List<Cloudlet> 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<Pe> 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));
}
}
Loading