Skip to content

Commit 939f99c

Browse files
Add an option (-p) to gradually increase the allocation rate (openjdk#60)
* Add an option (-p) to gradually increase the allocation rate over the given time period * Extract constant for nanos per second
1 parent 4073f84 commit 939f99c

File tree

7 files changed

+67
-10
lines changed

7 files changed

+67
-10
lines changed

HyperAlloc/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ There is also an experimental "spiky" allocator that can be used to simulate app
102102

103103
* -z < allocation smoothness factor>, default: none (this is not enabled by default) Values may range from 0.0 to 1.0 (inclusive).
104104

105+
In order to calm the allocation rate during startup, HyperAlloc can gradually increase the allocation rate over a period
106+
of time defined by the 'ramp up seconds' option. The ramp up increases from zero to the maximum following a sinusoidal curve.
107+
108+
* -p < ramp up seconds >, default: none, the initial allocation rate is the maximum.
105109
### Example
106110

107111
We normally use [JHiccup](https://www.azul.com/jhiccup/) to measure JVM pauses. You can either download it from its [website](https://www.azul.com/jhiccup-2/), or build it from the source code in its [GitHub repo](https://github.com/giltene/jHiccup). You can also use GC logs to measure safepoint times, allocation stalls, and Garbage Collection pauses. In the example below, we run HyperAlloc for the Shenandoah collector for 10 minutes using a 16Gb/s allocation rate and with 32Gb of a 64Gb heap occupied by long-lived objects.

HyperAlloc/src/main/java/com/amazon/corretto/benchmark/hyperalloc/SimpleRunConfig.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class SimpleRunConfig {
2020
private String logFile = "output.csv";
2121
private String allocationLogFile = null;
2222
private Double allocationSmoothnessFactor = null;
23+
private double rampUpSeconds = 0.0;
2324

2425
/**
2526
* Parse input arguments from a string array.
@@ -61,6 +62,8 @@ public SimpleRunConfig(final String[] args) {
6162
allocationLogFile = args[++i];
6263
} else if (args[i].equals("-u")) {
6364
i++;
65+
} else if (args[i].equals("-p") || args[i].equals("--ramp-up-seconds")) {
66+
rampUpSeconds = Double.parseDouble(args[++i]);
6467
} else {
6568
usage();
6669
System.exit(1);
@@ -73,7 +76,8 @@ private void usage() {
7376
"[-u run type] [-a allocRateInMb] [-h heapSizeInMb] [-s longLivedObjectsInMb] " +
7477
"[-m midAgedObjectsInMb] [-d runDurationInSeconds ] [-t numOfThreads] [-n minObjectSize] " +
7578
"[-x maxObjectSize] [-r pruneRatio] [-f reshuffleRatio] [-c useCompressedOops] " +
76-
"[-l outputFile] [-b|-allocation-log logFile] [-z allocationSmoothness (0 to 1.0)]");
79+
"[-l outputFile] [-b|-allocation-log logFile] [-z allocationSmoothness (0 to 1.0)] " +
80+
"[-p rampUpSeconds ]");
7781
}
7882

7983
/**
@@ -91,13 +95,14 @@ private void usage() {
9195
* @param useCompressedOops Whether compressedOops is enabled.
9296
* @param logFile The name of the output .csv file.
9397
* @param allocationLogFile The name of the allocation log file.
98+
* @param rampUpSeconds Gradually increase allocation rate over this period of time.
9499
*/
95100
public SimpleRunConfig(final long allocRateInMbPerSecond, final double allocSmoothnessFactor,
96101
final int heapSizeInMb, final int longLivedInMb,
97102
final int midAgedInMb, final int durationInSecond, final int numOfThreads,
98103
final int minObjectSize, final int maxObjectSize, final int pruneRatio,
99104
final int reshuffleRatio, final boolean useCompressedOops, final String logFile,
100-
final String allocationLogFile) {
105+
final String allocationLogFile, final double rampUpSeconds) {
101106
this.allocRateInMbPerSecond = allocRateInMbPerSecond;
102107
this.allocationSmoothnessFactor = allocSmoothnessFactor;
103108
this.heapSizeInMb = heapSizeInMb;
@@ -112,6 +117,7 @@ public SimpleRunConfig(final long allocRateInMbPerSecond, final double allocSmoo
112117
this.useCompressedOops = useCompressedOops;
113118
this.logFile = logFile;
114119
this.allocationLogFile = allocationLogFile;
120+
this.rampUpSeconds = rampUpSeconds;
115121
}
116122

117123
public long getAllocRateInMbPerSecond() {
@@ -169,5 +175,9 @@ public Double getAllocationSmoothnessFactor() {
169175
public String getAllocationLogFile() {
170176
return allocationLogFile;
171177
}
178+
179+
public double getRampUpSeconds() {
180+
return rampUpSeconds;
181+
}
172182
}
173183

HyperAlloc/src/main/java/com/amazon/corretto/benchmark/hyperalloc/SimpleRunner.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ private List<Callable<Long>> createTasks(final ObjectStore store) {
121121
if (config.getAllocationSmoothnessFactor() == null) {
122122
factory = (ignored) -> createSingle(store, allocRateMbPerThread,
123123
durationInMs, config.getMinObjectSize(),
124-
config.getMaxObjectSize(), queueSize);
124+
config.getMaxObjectSize(), queueSize,
125+
config.getRampUpSeconds());
125126
} else {
126127
factory = (ignored) -> createBurstyAllocator(store, allocRateMbPerThread,
127128
durationInMs, config.getAllocationSmoothnessFactor(),
@@ -145,7 +146,6 @@ public AllocationRateLogger(String allocationLogFile) {
145146
allocationLoggerThread = new Thread(this);
146147
allocationLoggerThread.setName("HyperAlloc-Allocations");
147148
allocationLoggerThread.setDaemon(true);
148-
149149
}
150150

151151
public void start() {

HyperAlloc/src/main/java/com/amazon/corretto/benchmark/hyperalloc/TaskBase.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.ArrayDeque;
66
import java.util.concurrent.Callable;
77
import java.util.concurrent.TimeUnit;
8+
import java.util.function.Function;
89

910
public abstract class TaskBase {
1011
// The default maximum object size.
@@ -24,6 +25,8 @@ public abstract class TaskBase {
2425
// The maximum ratio to try make objects long lived.
2526
protected static final int MAX_LONG_LIVED_RATIO = 2;
2627

28+
private static final double NANOS_PER_SECOND = 1_000_000_000.0;
29+
2730
/**
2831
* Start all task runners.
2932
*/
@@ -38,7 +41,7 @@ public abstract class TaskBase {
3841
*/
3942
static Callable<Long> createSingle(final ObjectStore store, final long rateInMb, final long durationInMs) {
4043
return createSingle(store, rateInMb, durationInMs,
41-
DEFAULT_MIN_OBJECT_SIZE, DEFAULT_MAX_OBJECT_SIZE, DEFAULT_SURVIVOR_QUEUE_LENGTH);
44+
DEFAULT_MIN_OBJECT_SIZE, DEFAULT_MAX_OBJECT_SIZE, DEFAULT_SURVIVOR_QUEUE_LENGTH, 0.0);
4245
}
4346

4447
/**
@@ -52,19 +55,37 @@ static Callable<Long> createSingle(final ObjectStore store, final long rateInMb,
5255
* @return The unused allocation allowance during the run.
5356
*/
5457
static Callable<Long> createSingle(final ObjectStore store, final long rateInMb,
55-
final long durationInMs, final int minObjectSize, final int maxObjectSize, final int queueLength) {
58+
final long durationInMs, final int minObjectSize, final int maxObjectSize,
59+
final int queueLength, double rampUpSeconds) {
5660
return () -> {
5761
final long rate = rateInMb * 1024 * 1024;
5862
final ArrayDeque<AllocObject> survivorQueue = new ArrayDeque<>();
5963

60-
final long end = System.nanoTime() + durationInMs * 1000000;
64+
final long start = System.nanoTime();
65+
final long end = start + durationInMs * 1000000;
66+
Function<Double, Double> rampUp = null;
67+
if (rampUpSeconds > 0) {
68+
rampUp = sinusoidalRampUp(rate, rampUpSeconds);
69+
}
70+
6171
final TokenBucket throughput = new TokenBucket(rate);
6272
int longLivedRate = MAX_LONG_LIVED_RATIO;
6373
int longLivedCounter = longLivedRate;
6474

6575
while (System.nanoTime() < end) {
6676
long wave = 0L;
6777
while (wave < rate / 10) {
78+
if (rampUp != null) {
79+
final double elapsedSeconds = (System.nanoTime() - start) / NANOS_PER_SECOND;
80+
if (elapsedSeconds < rampUpSeconds) {
81+
double currentRate = rampUp.apply(elapsedSeconds);
82+
throughput.adjustThrottle((long)currentRate);
83+
} else {
84+
throughput.adjustThrottle(rate);
85+
rampUp = null;
86+
}
87+
}
88+
6889
if (!throughput.isThrottled()) {
6990
final AllocObject obj = AllocObject.create(minObjectSize, maxObjectSize, null);
7091
throughput.deduct(obj.getRealSize());
@@ -169,4 +190,19 @@ static Callable<Long> createBurstyAllocator(final ObjectStore store, final long
169190
return 0L;
170191
};
171192
}
193+
194+
private static Function<Double, Double> linearRampUp(long maxRate, double rampUpSeconds) {
195+
final double rampUpRate = maxRate / rampUpSeconds;
196+
return (Double elapsedSeconds) -> elapsedSeconds * rampUpRate;
197+
}
198+
199+
private static Function<Double, Double> sinusoidalRampUp(final long maxRate, final double rampUpSeconds) {
200+
return (Double elapsedSeconds) -> {
201+
// First, map the elapsed time to the domain over which our function (cosine)
202+
// takes the minimum and maximum values: cos(pi) -> -1, cos(2pi) -> +1. Then,
203+
// map the range of our function to a rate between 0 and the maximum.
204+
double radians = Math.PI * ((elapsedSeconds / rampUpSeconds) + 1);
205+
return ((Math.cos(radians) + 1) / 2) * maxRate;
206+
};
207+
}
172208
}

HyperAlloc/src/main/java/com/amazon/corretto/benchmark/hyperalloc/TokenBucket.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ public class TokenBucket {
1515
private static final long DEFAULT_TIME_SLICE = TimeUnit.MILLISECONDS.toNanos(10);
1616
private static final int DEFAULT_OVERDRAFT_RATIO = 10;
1717
private final long timeSlice;
18-
private final long limit;
1918
private final long overdraftLimit;
2019
private final Supplier<Long> clock;
20+
private long limit;
2121
private long current;
2222
private long resetAtNanoSecond;
2323

@@ -97,4 +97,11 @@ public long getCurrent() {
9797
public boolean isThrottled() {
9898
return clock.get() < this.resetAtNanoSecond && current <= 0;
9999
}
100+
101+
public void adjustThrottle(final long newLimit) {
102+
limit = newLimit / 100;
103+
if (current > limit) {
104+
current = limit;
105+
}
106+
}
100107
}

HyperAlloc/src/test/java/com/amazon/corretto/benchmark/hyperalloc/SimpleRunConfigTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ void DefaultStringsTest() {
3232
void ConstructorTest() {
3333
final SimpleRunConfig config = new SimpleRunConfig(16384L, 0.0, 32768, 256,
3434
32, 3000, 16, 256, 512,
35-
10, 20, false, "nosuch.csv", null);
35+
10, 20, false, "nosuch.csv", null, 0.0);
3636

3737
assertThat(config.getNumOfThreads(), is(16));
3838
assertThat(config.getAllocRateInMbPerSecond(), is(16384L));

HyperAlloc/src/test/java/com/amazon/corretto/benchmark/hyperalloc/TaskBaseTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ void SingleTaskTest() throws Exception {
1616

1717
@Test
1818
void ExerciseLongLivedTest() throws Exception {
19-
assertThat(TaskBase.createSingle(new ObjectStore(10), 10, 5000, 8192, 65535, 100).call(),
19+
assertThat(TaskBase.createSingle(new ObjectStore(10), 10, 5000, 8192, 65535, 100, 0.0).call(),
2020
lessThanOrEqualTo(0L));
2121
}
2222
}

0 commit comments

Comments
 (0)