21 | * Each task can be added to the suite. Once you have the tasks you need, then 22 | * all tasks can be benchmarked according to limits given in the run. 23 | * 24 | *
Arrays.sort(...)
, or IntStream.sorted()
?:
26 | * 27 | Random random = new Random(); 28 | 29 | // create an array of 10,000 random integer values. 30 | final int[] data = IntStream.generate(random::nextInt).limit(10000).toArray(); 31 | // create a sorted version, trust the algorithm for the moment. 32 | final int[] sorted = Arrays.stream(data).sorted().toArray(); 33 | // a way to ensure the value is in fact sorted. 34 | Predicate<int[]> validate = v -> Arrays.equals(v, sorted); 35 | 36 | // A stream-based way to sort an array of integers. 37 | Supplier<int[]> stream = () -> Arrays.stream(data).sorted().toArray(); 38 | 39 | // The traditional way to sort an array of integers. 40 | Supplier<int[]> trad = () -> { 41 | int[] copy = Arrays.copyOf(data, data.length); 42 | Arrays.sort(copy); 43 | return copy; 44 | }; 45 | 46 | UBench bench = new UBench("Sort Algorithms") 47 | .addTask("Functional", stream, validate); 48 | .addTask("Traditional", trad, validate); 49 | .press(10000) 50 | .report("With Warmup"); 51 | *52 | * 53 | * You can expect results similar to: 54 | * 55 | *
56 | 57 | With Warmup 58 | =========== 59 | 60 | Task Sort Algorithms -> Functional: (Unit: MILLISECONDS) 61 | Count : 10000 Average : 0.4576 62 | Fastest : 0.4194 Slowest : 4.1327 63 | 95Pctile : 0.5030 99Pctile : 0.6028 64 | TimeBlock : 0.493 0.436 0.443 0.459 0.458 0.454 0.457 0.458 0.463 0.456 65 | Histogram : 9959 19 21 1 66 | 67 | Task Sort Algorithms -> Traditional: (Unit: MILLISECONDS) 68 | Count : 10000 Average : 0.4219 69 | Fastest : 0.4045 Slowest : 3.6714 70 | 95Pctile : 0.4656 99Pctile : 0.5420 71 | TimeBlock : 0.459 0.417 0.418 0.417 0.416 0.416 0.416 0.419 0.423 0.417 72 | Histogram : 9971 18 10 1 73 | 74 | 75 | *76 | * 77 | * See {@link UStats} for more details on what the statistics mean. 78 | * 79 | * @author rolf 80 | * 81 | */ 82 | public final class UBench { 83 | 84 | private static final Logger LOGGER = UUtils.getLogger(UBench.class); 85 | 86 | /** 87 | * At most a billion iterations of any task will be attempted. 88 | */ 89 | public static final int MAX_RESULTS = 1_000_000_000; 90 | 91 | private final Map
14 | * With 3 tasks, A, B, C: 15 | * 16 | *
17 | * A1 A2 .. An 18 | * B1 B2 .. Bn 19 | * C1 C2 .. Cn 20 | *21 | * 22 | */ 23 | SEQUENTIAL(new SequentialExecutionModel()), 24 | 25 | /** 26 | * Allocate a separate thread to each task, and execute them all at once. 27 | *
28 | * With 3 tasks, A, B, C: 29 | *
30 | * A1 A2 .. An 31 | * B1 B2 .. Bn 32 | * C1 C2 .. Cn 33 | *34 | */ 35 | PARALLEL(new ParallelExecutionModel()), 36 | 37 | /** 38 | * Run one iteration from each task, then go back to the first task, repeat for all iterations. 39 | *
40 | * With 3 tasks, A, B, C: 41 | *
42 | * A1 A2 .. An 43 | * B1 B2 .. Bn 44 | * C1 C2 .. Cn 45 | *46 | */ 47 | INTERLEAVED(new InterleavedExecutionModel()); 48 | 49 | private final TaskExecutionModel model; 50 | 51 | private UMode(TaskExecutionModel model) { 52 | this.model = model; 53 | } 54 | 55 | /** 56 | * Package Private: return the model implementation 57 | * @return the actual implementation 58 | */ 59 | TaskExecutionModel getModel() { 60 | return model; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/tuis/ubench/UReport.java: -------------------------------------------------------------------------------- 1 | package net.tuis.ubench; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | import java.util.ArrayList; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | 12 | /** 13 | * The UReport class encapsulates the results that are produced by 14 | * {@link UBench#press(UMode, int, int, double, long, TimeUnit)} and exposes 15 | * some convenient reporting methods. 16 | */ 17 | public class UReport { 18 | 19 | /** 20 | * A Comparator which sorts collections of UStats by the 95th 21 | * percentile time (ascending - fastest first) 22 | */ 23 | public static final Comparator
[ [scale,nanos], ...]
112 | * @return a JSON formatted string containing the raw data.
113 | */
114 | public String toJSONString() {
115 |
116 | String rawdata = stats.stream().sorted(Comparator.comparingInt(UStats::getIndex))
117 | .map(sr -> sr.toJSONString())
118 | .collect(Collectors.joining(",\n ", "[\n ", "\n ]"));
119 |
120 | String fields = Stream.of(UStats.getJSONFields()).collect(Collectors.joining("\", \"", "[\"", "\"]"));
121 |
122 | String models = Stream.of(ScaleDetect.rank(this)).map(me -> me.toJSONString()).collect(Collectors.joining(",\n ", "[\n ", "\n ]"));
123 |
124 | return String.format("{ title: \"%s\",\n nano_tick: %d,\n models: %s,\n fields: %s,\n data: %s\n}",
125 | title, UUtils.getNanoTick(), models, fields, rawdata);
126 | }
127 |
128 | /**
129 | * Create an HTML document (with data and chart) plotting the performance.
130 | * @param target the destination to store the HTML document at.
131 | * @throws IOException if there is a problem writing to the target path
132 | */
133 | public void reportHTML(final Path target) throws IOException {
134 |
135 | Files.createDirectories(target.toAbsolutePath().getParent());
136 |
137 | LOGGER.info(() -> "Preparing HTML Report '" + title + "' to path: " + target);
138 |
139 | String html = UUtils.readResource("net/tuis/ubench/scale/UScale.html");
140 | Map
154 | * This method calls scale(Consumer, IntFunction, boolean)
with
155 | * the reusedata parameter set to true:
156 | *
157 | *
158 | * return scale(function, scaler, true); 159 | *160 | * 161 | * This means that the data will be generated once for each scale factor, 162 | * and reused. 163 | * 164 | * @param
201 | * This method calls scale(Consumer, IntFunction, boolean)
with
202 | * the reusedata parameter set to true:
203 | *
204 | *
205 | * return scale(function, scaler, true); 206 | *207 | * 208 | * This means that the data will be generated once for each scale factor, 209 | * and reused. 210 | * 211 | * @param
17 | * Presents various statistics related to the run times that are useful for
18 | * interpreting the run performance.
19 | */
20 | public final class UStats {
21 |
22 | /*
23 | * unit(Bounds|Factor|Order|Name) static members are a way of identifying
24 | * which time unit is most useful for displaying a time.
25 | *
26 | * A useful unit is one which presents the time as something between 0.1 and
27 | * 99.999. e.g. it is better to have 2.35668 milliseconds than 2356.82334
28 | * microseconds, or 0.00235 seconds.
29 | */
30 | private static final long[] unitBounds = buildUnitBounds();
31 | private static final double[] unitFactor = buildUnitFactors();
32 | private static final TimeUnit[] unitOrder = buildUnitOrders();
33 | private static final String[] unitName = buildUnitNames();
34 |
35 | private static final Map
92 | * The best unit is the one which has no zeros after the decimal, and at
93 | * most two digits before.
94 | *
95 | * The following are examples of "best" displays:
96 | *
114 | * For example, if you have the following nanosecond times
115 | *
169 | * Compute all derived statistics on the assumption that toString will be
170 | * called, and one comprehensive scan will have less effect than multiple
171 | * partial results.
172 | *
173 | * @param name
174 | * The name of the task that has been benchmarked
175 | * @param results
176 | * The nano-second run times of each successful run.
177 | */
178 | UStats(String suit, String name, int index, long[] results) {
179 | this.suite = suit;
180 | this.name = name;
181 | this.index = index;
182 |
183 | if (results == null || results.length == 0) {
184 | this.results = new long[0];
185 | fastest = 0;
186 | slowest = 0;
187 | p95ile = 0;
188 | p99ile = 0;
189 |
190 | average = 0;
191 | histogram = new int[0];
192 | unit = findBestUnit(fastest);
193 | } else {
194 | this.results = results;
195 | // tmp is only used to compute percentile results.
196 | long[] tmp = Arrays.copyOf(results, results.length);
197 | Arrays.sort(tmp);
198 |
199 | fastest = tmp[0];
200 | slowest = tmp[tmp.length - 1];
201 |
202 | p95ile = tmp[(int) (tmp.length * 0.95)];
203 | p99ile = tmp[(int) (tmp.length * 0.99)];
204 |
205 | long sum = LongStream.of(results).sum();
206 | average = sum / tmp.length;
207 | histogram = new int[logTwo(slowest, fastest) + 1];
208 | for (long t : tmp) {
209 | histogram[logTwo(t, fastest)]++;
210 | }
211 |
212 | unit = findBestUnit(fastest);
213 | }
214 |
215 | }
216 |
217 | /**
218 | * The nanosecond time of the 95th percentile run.
219 | *
220 | * @return the nanosecond time of the 95th percentile run.
221 | */
222 | public long get95thPercentileNanos() {
223 | return p95ile;
224 | }
225 |
226 | /**
227 | * The nanosecond time of the 99th percentile run.
228 | *
229 | * @return the nanosecond time of the 99th percentile run.
230 | */
231 | public long get99thPercentileNanos() {
232 | return p99ile;
233 | }
234 |
235 | /**
236 | * The nanosecond time of the fastest run.
237 | *
238 | * @return the nanosecond time of the fastest run.
239 | */
240 | public long getFastestNanos() {
241 | return fastest;
242 | }
243 |
244 | /**
245 | * The nanosecond time of the slowest run.
246 | *
247 | * @return the nanosecond time of the slowest run.
248 | */
249 | public long getSlowestNanos() {
250 | return slowest;
251 | }
252 |
253 | /**
254 | * The nanosecond time of the average run.
255 | *
256 | * Note, this is in nanoseconds (using integer division of the total time /
257 | * count). Any sub-nano-second error is considered irrelevant
258 | *
259 | * @return the nanosecond time of the average run.
260 | */
261 | public long getAverageRawNanos() {
262 | return average;
263 | }
264 |
265 | /**
266 | * Package Private: Used to identify the order in which the task was added to the UBench instance.
267 | * @return the index in UBench.
268 | */
269 | int getIndex() {
270 | return index;
271 | }
272 |
273 | /**
274 | * Identify what a good time Unit would be to present the results in these
275 | * statistics.
276 | *
277 | * Calculated as the equivalent of
278 | *
301 | * An example helps. If there are 200 results, and a request for 10 zones,
302 | * then return 10 double values representing the average time of the first
303 | * 20 runs, then the next 20, and so on, until the 10th zone contains the
304 | * average time of the last 20 runs.
305 | *
306 | * This is a good way to see the effects of warm-up times and different
307 | * compile levels
308 | *
309 | * @param zoneCount
310 | * the number of zones to compute
311 | * @param timeUnit
312 | * the unit in which to report the times
313 | * @return an array of times (in the given unit) representing the average
314 | * time for all runs in the respective zone.
315 | */
316 | public final double[] getZoneTimes(int zoneCount, TimeUnit timeUnit) {
317 | if (results.length == 0) {
318 | return new double[0];
319 | }
320 | double[] ret = new double[Math.min(zoneCount, results.length)];
321 | int perblock = results.length / ret.length;
322 | int overflow = results.length % ret.length;
323 | int pos = 0;
324 | double repFactor = unitFactor[timeUnit.ordinal()];
325 | for (int block = 0; block < ret.length; block++) {
326 | int count = perblock + (block < overflow ? 1 : 0);
327 | int limit = pos + count;
328 | long nanos = 0;
329 | while (pos < limit) {
330 | nanos += results[pos];
331 | pos++;
332 | }
333 | ret[block] = (nanos / repFactor) / count;
334 | }
335 | return ret;
336 | }
337 |
338 | /**
339 | * Compute a log-2-based histogram relative to the fastest run in the data
340 | * set.
341 | *
342 | * This gives a sense of what the general shape of the runs are in terms of
343 | * distribution of run times. The histogram is based on the fastest run.
344 | *
345 | * By way of an example, the output:
364 | * 99% of all runs completed in this time, or faster.
365 | *
366 | * @param timeUnit
367 | * the unit in which to report the times
368 | * @return the time of the 99th percentile in the given time unit.
369 | */
370 | public final double get99thPercentile(TimeUnit timeUnit) {
371 | return p99ile / unitFactor[timeUnit.ordinal()];
372 | }
373 |
374 | /**
375 | * The 95th percentile of runtimes.
376 | *
377 | * 95% of all runs completed in this time, or faster.
378 | *
379 | * @param timeUnit
380 | * the unit in which to report the times
381 | * @return the time of the 95th percentile in the given time unit.
382 | */
383 | public final double get95thPercentile(TimeUnit timeUnit) {
384 | return p95ile / unitFactor[timeUnit.ordinal()];
385 | }
386 |
387 | /**
388 | * Compute the average time of all runs (in milliseconds).
389 | *
390 | * @param timeUnit
391 | * the unit in which to report the times
392 | * @return the average time (in milliseconds)
393 | */
394 | public final double getAverage(TimeUnit timeUnit) {
395 | return average / unitFactor[timeUnit.ordinal()];
396 | }
397 |
398 | /**
399 | * Compute the slowest run (in milliseconds).
400 | *
401 | * @param timeUnit
402 | * the unit in which to report the times
403 | * @return The slowest run time (in milliseconds).
404 | */
405 | public final double getSlowest(TimeUnit timeUnit) {
406 | return slowest / unitFactor[timeUnit.ordinal()];
407 | }
408 |
409 | /**
410 | * Compute the fastest run (in milliseconds).
411 | *
412 | * @param timeUnit
413 | * the unit in which to report the times
414 | * @return The fastest run time (in milliseconds).
415 | */
416 | public final double getFastest(TimeUnit timeUnit) {
417 | return fastest / unitFactor[timeUnit.ordinal()];
418 | }
419 |
420 | @Override
421 | public String toString() {
422 | return formatResults(unit);
423 | }
424 |
425 | /**
426 | * Represent this UStats as a name/value JSON structure.
427 | *
428 | * @return this data as a JSON-formatted string.
429 | */
430 | public String toJSONString() {
431 | return TIME_PULLER.entrySet().stream()
432 | .map(me -> String.format("%s: %d", me.getKey(), me.getValue().applyAsLong(this)))
433 | .collect(Collectors.joining(", ", "{", "}"));
434 | }
435 |
436 | /**
437 | * Present the results from this task in a formatted string output.
438 | * @param tUnit the units in which to display the times (see {@link UStats#getGoodUnit() } for a suggestion).
439 | * @return A string representing the statistics.
440 | * @see UStats#getGoodUnit()
441 | */
442 | public String formatResults(TimeUnit tUnit) {
443 | double avg = getAverage(tUnit);
444 | double fast = getFastest(tUnit);
445 | double slow = getSlowest(tUnit);
446 | double t95p = get95thPercentile(tUnit);
447 | double t99p = get99thPercentile(tUnit);
448 | int width = Math.max(8, DoubleStream.of(avg, fast, slow, t95p, t99p).mapToObj(d -> String.format("%.4f", d))
449 | .mapToInt(String::length).max().getAsInt());
450 |
451 | return String.format("Task %s -> %s: (Unit: %s)\n"
452 | + " Count : %" + width + "d Average : %" + width + ".4f\n"
453 | + " Fastest : %" + width + ".4f Slowest : %" + width + ".4f\n"
454 | + " 95Pctile : %" + width + ".4f 99Pctile : %" + width + ".4f\n"
455 | + " TimeBlock : %s\n"
456 | + " Histogram : %s\n",
457 | suite, name, unitName[tUnit.ordinal()],
458 | results.length, avg,
459 | fast, slow,
460 | t95p, t99p,
461 | formatZoneTime(getZoneTimes(10, tUnit)),
462 | formatHisto(getDoublingHistogram()));
463 | }
464 |
465 | /**
466 | * Retrieve the number of iterations that were executed.
467 | * @return the number of runs.
468 | */
469 | public int getCount() {
470 | return results.length;
471 | }
472 |
473 | /**
474 | * The name of the UBench Suite this task was run in.
475 | * @return the suite name.
476 | */
477 | public String getSuiteName() {
478 | return suite;
479 | }
480 |
481 | /**
482 | * The name of the UBench task these statistics are from.
483 | * @return the task name.
484 | */
485 | public String getName() {
486 | return name;
487 | }
488 |
489 | }
--------------------------------------------------------------------------------
/src/main/java/net/tuis/ubench/UUtils.java:
--------------------------------------------------------------------------------
1 | package net.tuis.ubench;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.lang.Thread.UncaughtExceptionHandler;
7 | import java.nio.charset.StandardCharsets;
8 | import java.util.concurrent.atomic.AtomicLong;
9 | import java.util.logging.Handler;
10 | import java.util.logging.Level;
11 | import java.util.logging.Logger;
12 | import java.util.stream.LongStream;
13 | import java.util.stream.Stream;
14 |
15 | /**
16 | * Collection of common static utility methods used in other areas of the
17 | * package.
18 | *
19 | * @author rolf
20 | *
21 | */
22 | public final class UUtils {
23 |
24 | private UUtils() {
25 | // private constructor, no instances possible.
26 | }
27 |
28 | // Create a LOGGER instance for the **PACKAGE**.
29 | // This will be the owner of the ubench namespace.
30 | private static final Logger LOGGER = Logger.getLogger(UScale.class.getPackage().getName());
31 |
32 | /**
33 | * Simple wrapper that forces initialization of the package-level LOGGER
34 | * instance.
35 | *
36 | * @param clazz
37 | * The class to be logged.
38 | * @return A Logger using the name of the class as its hierarchy.
39 | */
40 | public static Logger getLogger(final Class> clazz) {
41 | if (!clazz.getPackage().getName().startsWith(LOGGER.getName())) {
42 | throw new IllegalArgumentException(String.format("Class %s is not a child of the package %s",
43 | clazz.getName(), LOGGER.getName()));
44 | }
45 | LOGGER.fine(() -> String.format("Locating logger for class %s", clazz));
46 | return Logger.getLogger(clazz.getName());
47 | }
48 |
49 | /**
50 | * Enable regular logging for all UBench code at the specified level.
51 | *
52 | * @param level
53 | * the level to log for.
54 | */
55 | public static void setStandaloneLogging(Level level) {
56 | LOGGER.setUseParentHandlers(false);
57 | for (Handler h : LOGGER.getHandlers()) {
58 | LOGGER.removeHandler(h);
59 | }
60 | StdoutHandler handler = new StdoutHandler();
61 | handler.setFormatter(new InlineFormatter());
62 | LOGGER.addHandler(handler);
63 |
64 | final UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
65 | Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
66 | LOGGER.log(Level.SEVERE, "Uncaught Exception in thread " + t.getName(), e);
67 | if (ueh != null) {
68 | ueh.uncaughtException(t, e);
69 | }
70 | });
71 |
72 | setLogLevel(level);
73 | }
74 |
75 | /**
76 | * Enable the specified debug level messages to be output. Note that both
77 | * this Logger and whatever Handler you use, have to be set to enable the
78 | * required log level for the handler to output the messages. If this UBench
79 | * code is logging 'stand alone' then this method will also change the
80 | * output level of the log handlers.
81 | *
82 | * @param level The log level to activate for future log levels.
83 | */
84 | public static void setLogLevel(Level level) {
85 | // all other ubench loggers inherit from here.
86 | LOGGER.finer("Changing logging from " + LOGGER.getLevel());
87 | LOGGER.setLevel(level);
88 | if (!LOGGER.getUseParentHandlers()) {
89 | LOGGER.setLevel(level);
90 | Stream.of(LOGGER.getHandlers()).forEach(h -> h.setLevel(level));
91 | }
92 | LOGGER.finer("Changed logging to " + LOGGER.getLevel());
93 | }
94 |
95 | private static final AtomicLong NANO_TICK = new AtomicLong(-1);
96 | /**
97 | * The minimum increment that the System.nanotime() can do. The nanotime
98 | * value returned by the system does not necessarily increment by 1, this
99 | * value indicates the smallest observed increment of the timer.
100 | * @return The number of nanoseconds in the smallest recorded clock tick.
101 | */
102 | public static long getNanoTick() {
103 | synchronized(NANO_TICK) {
104 | long tick = NANO_TICK.get();
105 | if (tick > 0) {
106 | return tick;
107 | }
108 | tick = computeTick();
109 | NANO_TICK.set(tick);
110 | return tick;
111 | }
112 | }
113 |
114 | private static final long singleTick() {
115 |
116 | final long start = System.nanoTime();
117 | long end = start;
118 | while (end == start) {
119 | end = System.nanoTime();
120 | }
121 | return end - start;
122 |
123 | }
124 |
125 | private static long computeTick() {
126 | final long ticklen = LongStream.range(0, 1000).map(i -> singleTick()).min().getAsLong();
127 | LOGGER.fine(() -> String.format("Incremental System.nanotime() tick is %d", ticklen));
128 | return ticklen;
129 | }
130 |
131 | /**
132 | * Load a resource stored in the classpath, as a String.
133 | *
134 | * @param path
135 | * the system resource to read
136 | * @return the resource as a String.
137 | */
138 | public static String readResource(String path) {
139 | final long start = System.nanoTime();
140 | try (InputStream is = UScale.class.getClassLoader().getResourceAsStream(path);) {
141 | int len = 0;
142 | byte[] buffer = new byte[2048];
143 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
144 | while ((len = is.read(buffer)) >= 0) {
145 | baos.write(buffer, 0, len);
146 | }
147 | return new String(baos.toByteArray(), StandardCharsets.UTF_8);
148 | } catch (IOException e) {
149 | LOGGER.log(Level.WARNING, e, () -> "IOException loading resource " + path);
150 | throw new IllegalStateException("Unable to read class loaded stream " + path, e);
151 | } catch (RuntimeException re) {
152 | LOGGER.log(Level.WARNING, re, () -> "Unexpected exception loading resource " + path);
153 | throw re;
154 | } finally {
155 | LOGGER.fine(() -> String.format("Loaded resource %s in %.3fms", path,
156 | (System.nanoTime() - start) / 1000000.0));
157 | }
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/net/tuis/ubench/scale/MathEquation.java:
--------------------------------------------------------------------------------
1 | package net.tuis.ubench.scale;
2 |
3 | import java.util.Arrays;
4 | import java.util.function.DoubleUnaryOperator;
5 | import java.util.stream.Collectors;
6 | import java.util.stream.DoubleStream;
7 |
8 | /**
9 | * A function that describes one of the standard scaling equations
10 | *
11 | * @author Simon Forsberg
12 | */
13 | public class MathEquation {
14 |
15 | private final DoubleUnaryOperator equation;
16 | private final double[] parameters;
17 | private final String format;
18 | private final double rSquared;
19 | private final MathModel model;
20 |
21 | /**
22 | * A function that describes one of the standard scaling equations
23 | *
24 | * @param model
25 | * the model this function is based on
26 | * @param equation
27 | * the x-to-y equation for this instance
28 | * @param parameters
29 | * the parameters describing the required coefficients in the
30 | * equation
31 | * @param format
32 | * the string format for the equation
33 | * @param rSquared
34 | * the measure of the accuracy of this equation against the
35 | * actual results.
36 | */
37 | public MathEquation(MathModel model, DoubleUnaryOperator equation, double[] parameters, String format,
38 | double rSquared) {
39 | this.model = model;
40 | this.equation = equation;
41 | this.parameters = parameters;
42 | this.format = format;
43 | this.rSquared = rSquared;
44 | }
45 |
46 | /**
47 | * Get a text-based description of this equation
48 | *
49 | * @return the string version of this equation
50 | */
51 | public String getDescription() {
52 | Object[] params = new Object[parameters.length];
53 | for (int i = 0; i < parameters.length; i++) {
54 | params[i] = parameters[i];
55 | }
56 | return String.format(format, params);
57 | }
58 |
59 | /**
60 | * Get the parameters representing the various coefficients in this equation
61 | *
62 | * @return a copy of the equation coefficients
63 | */
64 | public double[] getParameters() {
65 | return Arrays.copyOf(parameters, parameters.length);
66 | }
67 |
68 | /**
69 | * Get a function representing the x-to-y transform for this eqation
70 | *
71 | * @return an equation transforming an x position to a y offset.
72 | */
73 | public DoubleUnaryOperator getEquation() {
74 | return equation;
75 | }
76 |
77 | /**
78 | * A String.format template for presenting the equation with its parameters
79 | *
80 | * @return A String format specification suitable for the parameters.
81 | */
82 | public String getFormat() {
83 | return format;
84 | }
85 |
86 | /**
87 | * Get the accuracy measure for this equation against the actual results. A
88 | * value of 1.0 is a compelte match against the actual results, a value
89 | * close to zero is a fail-to-match
90 | *
91 | * @return the r-squared value representing this equation's accuracy
92 | */
93 | public double getRSquared() {
94 | return rSquared;
95 | }
96 |
97 | /**
98 | * Get the mathematical model this equation is based on.
99 | *
100 | * @return the underlying model.
101 | */
102 | public MathModel getModel() {
103 | return model;
104 | }
105 |
106 | @Override
107 | public String toString() {
108 | return getDescription() + " with precision " + rSquared;
109 | }
110 |
111 | /**
112 | * Convert this equation in to a JSON string representing the vital
113 | * statistics of the equation.
114 | *
115 | * @return a JSON interpretation of this equation.
116 | */
117 | public String toJSONString() {
118 | String parms = DoubleStream.of(parameters)
119 | .mapToObj(d -> String.format("%f", d))
120 | .collect(Collectors.joining(", ", "[", "]"));
121 |
122 | String desc = String.format(format, DoubleStream.of(parameters).mapToObj(Double::valueOf).toArray());
123 | return String.format(
124 | "{name: \"%s\", valid: %s, format: \"%s\", description: \"%s\", parameters: %s, rsquare: %f}",
125 | model.getName(), isValid() ? "true" : "false", format, desc, parms, rSquared);
126 | }
127 |
128 | /**
129 | * Indicate whether this equation is a suitable match against the actual
130 | * data.
131 | *
132 | * @return true if this equation is useful when representing the actual
133 | * data's scalability
134 | */
135 | public boolean isValid() {
136 | return Math.abs(parameters[0]) >= 0.001 && rSquared != Double.NEGATIVE_INFINITY && !Double.isNaN(rSquared);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/net/tuis/ubench/scale/MathModel.java:
--------------------------------------------------------------------------------
1 | package net.tuis.ubench.scale;
2 |
3 | import java.util.Arrays;
4 | import java.util.function.DoubleUnaryOperator;
5 | import java.util.function.Function;
6 |
7 | /**
8 | * A MathModel describes an abstract scaling function that could massaged to fit
9 | * the actual scaling of empirical data.
10 | *
11 | * @author Simon Forsberg
12 | */
13 | public class MathModel {
14 |
15 | private final Function
97 | *
101 | *
102 | * in contrast, the following would not be suggested as "best" units:
103 | *
104 | *
105 | *
109 | *
110 | * It is suggested that you should find the best unit for the shortest time
111 | * value you will have in your data, and then use that same unit to display
112 | * all times.
113 | * [5432, 8954228, 665390, 492009]
you should find the unit for
116 | * the shortest (5432) which will be TimeUnit.MICROSECONDS, and end up with
117 | * the display of:
118 | *
119 | *
120 | * 5.432
121 | * 8954.228
122 | * 665.390
123 | * 492.009
124 | *
125 | *
126 | * @param time
127 | * the time to display (in nanoseconds)
128 | * @return A Time Unit that will display the nanosecond time well.
129 | */
130 | public static TimeUnit findBestUnit(long time) {
131 | for (int i = 1; i < unitOrder.length; i++) {
132 | if (unitBounds[unitOrder[i].ordinal()] > time) {
133 | return unitOrder[i - 1];
134 | }
135 | }
136 | return unitOrder[unitOrder.length - 1];
137 | }
138 |
139 | private static final String formatHisto(int[] histogramByXFactor) {
140 | return IntStream.of(histogramByXFactor).mapToObj(i -> String.format("%5d", i)).collect(Collectors.joining(" "));
141 | }
142 |
143 | private static final String formatZoneTime(double[] zoneTimes) {
144 | return DoubleStream.of(zoneTimes).mapToObj(d -> String.format("%.3f", d)).collect(Collectors.joining(" "));
145 | }
146 |
147 | private static final int logTwo(long numerator, long denominator) {
148 | long dividend = numerator / denominator;
149 | long tip = Long.highestOneBit(dividend);
150 | return Long.numberOfTrailingZeros(tip);
151 | }
152 |
153 | private final long[] results;
154 | private final long fastest;
155 | private final long slowest;
156 | private final long average;
157 | private final String suite;
158 | private final String name;
159 | private final int[] histogram;
160 | private final long p95ile;
161 | private final long p99ile;
162 | private final TimeUnit unit;
163 | private final int index;
164 |
165 | /**
166 | * Package Private: Construct statistics based on the nanosecond times of
167 | * multiple runs.
168 | * findBestUnit(getFastestRawNanos())
279 | *
280 | * @return A time unit useful for scaling these statistical results.
281 | * @see UStats#findBestUnit(long)
282 | */
283 | public TimeUnit getGoodUnit() {
284 | return unit;
285 | }
286 |
287 | /**
288 | * Get the raw data the statistics are based off.
289 | *
290 | * @return (a copy of) the individual test run times (in nanoseconds, and in order of
291 | * execution).
292 | */
293 | public long[] getData() {
294 | return Arrays.copyOf(results, results.length);
295 | }
296 |
297 | /**
298 | * Summarize the time-progression of the run time for each iteration, in
299 | * order of execution (in milliseconds).
300 | * 100, 50, 10, 1, 0, 1
would
346 | * suggest that:
347 | *
348 | *
354 | *
355 | * @return an int array containing the time distribution frequencies.
356 | */
357 | public final int[] getDoublingHistogram() {
358 | return Arrays.copyOf(histogram, histogram.length);
359 | }
360 |
361 | /**
362 | * The 99th percentile of runtimes.
363 | * createPolynom(4)
would create an O(n4)
50 | * model.
51 | *
52 | * @param degree
53 | * the polynomial degree
54 | * @return a MathModel representing the specified degree.
55 | */
56 | public static MathModel createPolynom(int degree) {
57 | if (degree < 0) {
58 | throw new IllegalArgumentException("Degree must be positive");
59 | }
60 | double[] params = new double[degree + 1];
61 | params[0] = 1;
62 | StringBuilder format = new StringBuilder();
63 | for (int i = degree; i >= 0; i--) {
64 | if (i > 0) {
65 | format.append("%f*n");
66 | if (i > 1) {
67 | format.append('^');
68 | format.append(i);
69 | }
70 | format.append(" + ");
71 | }
72 | }
73 | format.append("%f");
74 | FunctionUScale Report - ${TITLE}
134 |
135 |
138 |
140 |
156 |
141 |
155 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
160 |