blob: 77209d6040a990fdfd0d19e739cd466c4ced1e7d [file] [log] [blame]
/*
* 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.
*/
/**
* @author Salikh Zakirov
*/
package gc;
import java.util.Random;
/**
* Measures the throughput and the pause times on
* random object allocation.
* The test is very slow on interpreter and may take more than 5 minutes
* to complete.
*/
public class Mark extends Thread {
static Mark workers[];
static Mark sleepers[];
final static long kb = 1024;
final static long Mb = 1024*kb;
final static long Gb = 1024*Mb;
static int sleeper_number = 10;
static int worker_number = 2;
static long allocate_amount = 400*Mb;
static long live_amount = 100*Mb;
static long pause_threshold = 120;
static long throughput_pause_threshold = 400;
static boolean verbose = false;
static boolean report_on_pause = false;
static int report_interval = 1000;
public static void main(String[] args) {
init();
Thread reporter = new Reporter();
reporter.start();
workers = new Mark[worker_number];
for (int i = 0; i < workers.length; i++) {
workers[i] = new Mark(
allocate_amount/worker_number,
live_amount/worker_number);
workers[i].start();
}
sleepers = new Mark[sleeper_number];
for (int i = 0; i < sleepers.length; i++) {
sleepers[i] = new Mark(0,0);
sleepers[i].setDaemon(true);
sleepers[i].start();
}
for (int i = 0; i < workers.length; i++) {
try {
workers[i].join();
} catch (Exception e) {}
}
try {
reporter.interrupt();
reporter.join();
} catch (Exception e) {}
System.out.println("PASSED");
}
static void init() {
try {
int n = Integer.parseInt(System.getProperty("workers"));
if (n > 0 && n < 10000) {
worker_number = n;
}
} catch (Exception e) {}
try {
int n = Integer.parseInt(System.getProperty("threads"));
if (n > 0 && n < worker_number) {
sleeper_number = n - worker_number;
}
} catch (Exception e) {}
try {
int n = Integer.parseInt(System.getProperty("sleepers"));
if (n > 0 && n < 10000) {
sleeper_number = n;;
}
} catch (Exception e) {}
try {
long n = parseSize(System.getProperty("allocate"));
if (n > 0)
allocate_amount = n;
} catch (Exception e) {}
try {
long n = parseSize(System.getProperty("live"));
if (n > 0)
live_amount = n;
} catch (Exception e) {}
try {
int n = Integer.parseInt(System.getProperty("interval"));
if (n > 0)
report_interval = n;
} catch (Exception e) {}
verbose = (System.getProperty("silent") == null);
report_on_pause = (System.getProperty("report_on_pause") != null);
if (verbose) {
System.out.println("allocating " + mb(allocate_amount) + " Mb"
+ " on " + worker_number + " workers with " + sleeper_number + " sleepers"
+ ", live size " + mb(live_amount) + " Mb"
+ ", pause threshold " + pause_threshold + " ms"
);
}
}
public static long parseSize (String x) throws NumberFormatException {
int len = x.length();
long factor = 1;
switch (x.charAt(len-1)) {
case 'k': factor = kb; len--; break;
case 'm': factor = Mb; len--; break;
case 'g': factor = Gb; len--; break;
}
long result = Long.parseLong(x.substring(0,len));
return factor * result;
}
public static long mb (long size) {
return (size + Mb/2)/Mb;
}
// benchmarking information
static long abs_max_pause = 0;
static long max_pause = 0;
static long min_pause = Long.MAX_VALUE;
static long sum_pause = 0;
static long sum_time = 0;
static int num_pause = 0;
static long min_throughput = Long.MAX_VALUE;
static long abs_min_throughput = Long.MAX_VALUE;
static long sum_min_throughput = 0;
static int num_min_throughput = 0;
static long sum_throughput = 0;
static long last_system_pause_start = 0;
static long last_system_pause_end = 0;
static int num_system_pause_reports = 0;
static long sum_system_pause = 0;
static int num_system_pause = 0;
static long start_time = System.currentTimeMillis();
static long end_time = start_time;
static long max_system_pause = 0;
// per-thread throughput measurements
long cumulative_amount = 0;
long cumulative_time = 0;
// last - end of the previous pause
// start - start of the pause
// end - end of the pause
// amount - the amount allocated since last pause
void record(long last, long start, long end, long amount) {
synchronized (Mark.class) {
long time = end - last;
long pause = end - start;
// system pause = intersection of pauses on all worker threads
// we assume that the records are coming fairly quick
if (start >= last_system_pause_end) {
// We got new pause, abandon old data
last_system_pause_start = start;
last_system_pause_end = end;
num_system_pause_reports = 1;
} else {
// We are dealing with the same pause
// Refine the system pause boundaries
if (start > last_system_pause_start)
last_system_pause_start = start;
if (end < last_system_pause_end)
last_system_pause_end = end;
num_system_pause_reports += 1;
// Check if we had enough reports to consider
// the pause system. Note, that if there is just 1
// worker thread, no system pause accounting will take place.
if (num_system_pause_reports == worker_number) {
// record the system pause if it is valid
if (last_system_pause_start < last_system_pause_end) {
long system_pause = last_system_pause_end - last_system_pause_start;
sum_system_pause += system_pause;
num_system_pause += 1;
if (system_pause > max_system_pause)
max_system_pause = system_pause;
if (report_on_pause)
Mark.class.notify();
}
} else if (num_system_pause_reports >= worker_number) {
System.out.println("WARNING: too many pause reports!");
}
}
sum_time += time;
sum_pause += pause;
num_pause += 1;
sum_throughput += amount; // use sum_time to get average per-thread throughput
if (pause > max_pause) {
max_pause = pause;
}
if (pause > abs_max_pause) {
abs_max_pause = pause;
}
if (pause < min_pause) {
min_pause = pause;
}
cumulative_time += time;
cumulative_amount += amount;
if (cumulative_time > throughput_pause_threshold) {
long throughput = cumulative_amount / cumulative_time;
if (throughput < min_throughput) {
min_throughput = throughput;
}
if (throughput < abs_min_throughput) {
abs_min_throughput = throughput;
}
cumulative_time = 0;
cumulative_amount = 0;
}
end_time = end; // update the total clock time measurement
} // synchronized (Mark.class)
} // record()
static class Reporter extends Thread {
public void run() {
while (!isInterrupted()) {
synchronized (Mark.class) {
try {
Mark.class.wait(report_interval);
} catch (InterruptedException e) {
break;
}
}
report();
}
stats();
}
void report() {
// nothing to report
if (0 == num_pause) return;
long now = System.currentTimeMillis();
long avg_pause = sum_pause / num_pause;
long avg_throughput = sum_throughput / sum_time;
long sys_throughput = sum_throughput / (now - start_time);
if (verbose) {
long percent = 100l * sum_throughput / allocate_amount;
String report =
"Pause max " + max_pause + " ms, "
+ (min_pause < Long.MAX_VALUE ? "min " + min_pause + " ms, " : "")
+ "avg " + avg_pause + " ms, "
+ "sys " + sum_system_pause + " ms";
final String spaces = " ";
// a little bit of report alignment
int len = 60 - report.length();
if (len < 0) len = 8 - report.length()%8;
report += spaces.substring(0, len);
if (sys_throughput > 0 || avg_throughput > 0)
report += "Throughput (bytes/ms) "
+ "avg " + avg_throughput + ", "
+ "sys " + sys_throughput
+ (min_throughput < Long.MAX_VALUE ? ", min " + min_throughput : "")
+ " -- " + percent + "%";
System.out.println(report);
}
sum_min_throughput += min_throughput;
num_min_throughput += 1;
min_throughput = Long.MAX_VALUE;
min_pause = Long.MAX_VALUE;
max_pause = 0;
}
void stats() {
if (0 == num_pause) {
System.out.println(
"No registered pauses with threshold "
+ pause_threshold + " ms");
return;
}
long avg_pause = sum_pause / num_pause;
long avg_throughput = sum_throughput / sum_time;
long sys_throughput = sum_throughput / (end_time - start_time);
System.out.println("=================");
System.out.println(""
+ "time " + sum_time + " ms"
+ " / " + worker_number + " worker threads, "
+ sleeper_number + " sleeper threads, "
+ "clock " + (end_time - start_time) + " ms"
);
//System.out.println("Pause threshold " + pause_threshold + " ms");
System.out.println(""
+ "thread pause " + sum_pause + " ms"
+ " / " + num_pause + ", "
+ "max " + abs_max_pause + " ms, "
+ "avg " + avg_pause + " ms"
+ ", threshold " + pause_threshold + " ms"
);
if (num_system_pause > 0) System.out.println(""
+ "system pause " + sum_system_pause + " ms"
+ " / " + num_system_pause + ", "
+ "max " + max_system_pause + " ms, "
+ "avg " + (sum_system_pause/num_system_pause) + " ms"
);
System.out.println("throughput (bytes/ms) "
+ "avg " + avg_throughput + ", "
+ "sys " + sys_throughput + ", "
+ (abs_min_throughput < Long.MAX_VALUE ? "min " + abs_min_throughput : "")
);
System.out.println("=================");
}
}
boolean sleeper = false;
long target; // how many bytes the thread needs to allocate
long amount; // how many bytes the thread already allocated
long live_target; // how many bytes the threads needs to keep alive
long live = 0; // how many bytes the threads has live now
Object links[][];
public Mark(long allocate, long live) {
if (0 == allocate) {
sleeper = true;
} else {
this.target = allocate;
this.live_target = live;
// reserve memory to keep objects live
links = new Object[2048][];
int size = (int)(live_target / links.length / 64 + 1);
for (int i = 0; i < links.length; i++) {
links[i] = new Object[size];
}
live_target -= links.length * size * 4 + 12;
}
}
public void run() {
if (sleeper) sleep();
amount = 0;
long last_amount = amount;
long last = System.currentTimeMillis();
long start = 0, end = 0;
while (amount < target) {
start = System.currentTimeMillis();
Object o = allocate();
end = System.currentTimeMillis();
amount += size(o);
if (end - start > pause_threshold) {
record(last, start, end, amount - last_amount);
last_amount = amount;
last = end;
}
handle(o);
}
record(last, start, end, amount - last_amount);
}
/// Mostly sleeping function
void sleep() {
while (true) {
// discard the allocated object immediately
allocate();
try {
Thread.sleep(random.nextInt(100) + 1);
} catch (Exception e) {}
}
}
void handle(Object o) {
int size = size(o);
// do nothing if the object is larger than live size target
if (size > live_target) return;
// store the object
live += store(o);
// free objects at random to keep live size around target
while (live > live_target) {
int freed = remove();
if (0 == freed) break;
live -= freed;
}
}
// returns the delta of the live size
int store(Object o) {
int i = random.nextInt(links.length);
int j = random.nextInt(links[i].length);
int old_size = size(links[i][j]);
links[i][j] = o;
int size = size(links[i][j]);
return (size - old_size);
}
// find one non-null element in links[][],
// starting from links[i][j]
// remove it and return its size
int remove(int i, int j) {
while (i < links.length && j < links[i].length && null == links[i][j] ) {
j++;
if (j >= links[i].length) {
j = 0;
i++;
}
}
if (i < links.length && j < links[i].length) {
int size = size(links[i][j]);
links[i][j] = null;
return size;
}
return 0;
}
// removes one random element from links[][]
// and returns its size
int remove() {
int i = random.nextInt(links.length);
int j = random.nextInt(links[i].length);
int size = remove(i,j);
if (size > 0) return size;
return remove(0,0);
}
static Random random = new Random();
static Object allocate() {
if (random.nextInt(10) < 4) {
// array probability 0.4
return allocateArray(random_size(array_size_distribution));
} else {
// regular object probability 0.6
return allocateObject(random_size(object_size_disribution));
}
}
/// logarithm base 2
static int log(int n) {
if (n < 0) n = -n;
int l = 0;
while (n > 1) {
l += 1;
n = n >> 1;
}
return l;
}
static int[] convert_probability_to_distribution(int[] probability) {
int len = probability.length;
int[] distribution = new int[len];
for (int i = 1; i < len; i++) {
distribution[i] = distribution[i-1] + probability[i];
}
return distribution;
}
static int random_size(int[] size_distribution) {
int len = size_distribution.length;
int limit = size_distribution[len-1];
int n = random.nextInt(limit);
int i;
for (i = 1; i < array_size_distribution.length; i++) {
if (n < array_size_distribution[i]) break;
}
if (i < len - 1) {
// 0 - 240
return (4 + 8*i);
} else {
// 248+
int size = 256;
if (random.nextInt(2) == 0) size *= 2; // 512
else return size;
if (random.nextInt(2) == 0) size *= 2; // 1024
else return size;
if (random.nextInt(2) == 0) size *= 2; // 2048
else return size;
if (random.nextInt(2) == 0) size *= 2; // 4096
else return size;
if (random.nextInt(2) == 0) size *= 2; // 8192
else return size;
if (random.nextInt(2) == 0) size *= 2; // 16384
else return size;
if (random.nextInt(2) == 0) size *= 2; // 32768
else return size;
if (random.nextInt(2) == 0) size *= 2; // 65536
else return size;
if (random.nextInt(2) == 0) size *= 2; // 131072
else return size;
if (random.nextInt(2) == 0) size *= 2; // 262144
else return size;
if (random.nextInt(2) == 0) size *= 2; // 512k
else return size;
if (random.nextInt(2) == 0) size *= 2; // 1024k
else return size;
if (random.nextInt(2) == 0) size *= 2; // 2048k
else return size;
if (random.nextInt(2) == 0) size *= 2; // 4096k
else return size;
if (random.nextInt(2) == 0) size *= 2; // 8M
else return size;
if (random.nextInt(2) == 0) size *= 2; // 16M
return size;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// the data got from EHWA with -Dcharacterize_heap=on, ObjectSizeHistogram.txt, numArray###
static int array_size_probability[] = new int[] {
/* 0 */ 0, /* 8 */ 137, /* 16 */ 2087, /* ... */ 1188, 707, 646, 604, 620, 1216, 361, 458, 240, 1053, 387,
676, 624, 474, 333, 248, 176, 112, 123, 69, 58, 30, 36, 24, 25, 17, 12, /* 240 */ 12, /* 248+ */ 464
};
static int array_size_distribution[] = convert_probability_to_distribution(array_size_probability);
static int object_size_probability[] = new int[] {
/* 0 */ 0, /* 8 */ 1324, /* 16 */ 987, /* 24 */ 14610, /* 32 */ 405, 1737, 433, 14,
66,45,10,4,1,4,3,2,0,0,0,0,0,0,6,0,0,3,0,0,0,0,0, /* 248+ */ 3
};
static int object_size_disribution[] = convert_probability_to_distribution(object_size_probability);
///////////////////////////////////////////////////////////////////////////////////////////
static Object allocateArray(int size) {
if (random.nextInt(3) == 0) return new byte[size-12];
else if (random.nextInt(2) == 0) return new Object[(size-12)/4];
else return new double[(size-12)/8];
}
static Object allocateObject(int size) {
switch (size) {
case 8: return new Object();
case 16: return new Object16();
case 24: return new Object24();
case 32: return new Object32();
case 40: return new Object40();
case 48: return new Object48();
case 56: return new Object56();
case 64: return new Object64();
case 72: return new Object72();
case 80: return new Object80();
case 88: return new Object88();
case 96: return new Object96();
case 104: return new Object104();
case 112: return new Object112();
case 120: return new Object120();
case 128: return new Object128();
case 136: return new Object136();
case 144: return new Object144();
case 152: return new Object152();
case 160: return new Object160();
case 168: return new Object168();
case 176: return new Object176();
case 184: return new Object184();
case 192: return new Object192();
case 200: return new Object200();
case 208: return new Object208();
case 216: return new Object216();
case 224: return new Object224();
case 232: return new Object232();
case 240: return new Object240();
case 248: return new Object248();
case 256: return new Object256();
default: return new Object264();
}
}
//static byte_array_class = new byte[0].getClass();
static int size(Object o) {
if (null == o) return 0;
if (o instanceof byte[]) {
byte[] b = (byte[]) o;
return b.length + 12;
} else if (o instanceof Object[]) {
Object[] a = (Object[])o;
return a.length*4 + 12;
} else if (o instanceof double[]) {
double[] a = (double[])o;
return a.length*8 + 12;
} else if (o instanceof Object264) return 264;
else if (o instanceof Object256) return 256;
else if (o instanceof Object248) return 248;
else if (o instanceof Object240) return 240;
else if (o instanceof Object232) return 232;
else if (o instanceof Object224) return 224;
else if (o instanceof Object216) return 216;
else if (o instanceof Object208) return 208;
else if (o instanceof Object200) return 200;
else if (o instanceof Object192) return 192;
else if (o instanceof Object184) return 184;
else if (o instanceof Object176) return 176;
else if (o instanceof Object168) return 168;
else if (o instanceof Object160) return 160;
else if (o instanceof Object152) return 152;
else if (o instanceof Object144) return 144;
else if (o instanceof Object136) return 136;
else if (o instanceof Object128) return 128;
else if (o instanceof Object120) return 120;
else if (o instanceof Object112) return 112;
else if (o instanceof Object104) return 104;
else if (o instanceof Object96) return 96;
else if (o instanceof Object88) return 88;
else if (o instanceof Object80) return 80;
else if (o instanceof Object72) return 72;
else if (o instanceof Object64) return 64;
else if (o instanceof Object56) return 56;
else if (o instanceof Object48) return 48;
else if (o instanceof Object40) return 40;
else if (o instanceof Object32) return 32;
else if (o instanceof Object24) return 24;
else if (o instanceof Object16) return 16;
else return 8; /// XXX all unknown objects will be considered Objects (size == 8)
}
static class Object16 { Object f16; byte b16; }
static class Object24 extends Object16 { double f24; }
static class Object32 extends Object24 { Object f32; Object ff32; }
static class Object40 extends Object32 { Object f40; short s40; }
static class Object48 extends Object40 { long f48; }
static class Object56 extends Object48 { Object f56; byte b56; }
static class Object64 extends Object56 { long f64; }
static class Object72 extends Object64 { double f72; }
static class Object80 extends Object72 { Object f80; int i80; }
static class Object88 extends Object80 { Object f88; int i88; }
static class Object96 extends Object88 { long f96; }
static class Object104 extends Object96 { Object f104; Object o104; }
static class Object112 extends Object104 { Object f112; byte b112; }
static class Object120 extends Object112 { long f120; }
static class Object128 extends Object120 { long f128; }
static class Object136 extends Object128 { Object f136; int i136; }
static class Object144 extends Object136 { double f144; }
static class Object152 extends Object144 { Object f152; Object o152; }
static class Object160 extends Object152 { double f160; }
static class Object168 extends Object160 { Object f168; Object o168; }
static class Object176 extends Object168 { Object f176; byte b176; }
static class Object184 extends Object176 { long f184; }
static class Object192 extends Object184 { Object f192; Object[] a192; }
static class Object200 extends Object192 { double f200; }
static class Object208 extends Object200 { Object f208; Object[] a208; }
static class Object216 extends Object208 { long f216; }
static class Object224 extends Object216 { Object f224; int i224; }
static class Object232 extends Object224 { double f232; }
static class Object240 extends Object232 { Object f240; byte b240; }
static class Object248 extends Object240 { Object f248; Object o248; }
static class Object256 extends Object248 { double f256; }
static class Object264 extends Object256 { Object f264; Object[] a264; }
}