--- /dev/null 2017-07-24 12:48:32.758062323 -0700 +++ new/test/jdk/java/net/URL/URLMicroBenchmark.java 2017-12-11 09:02:42.654912073 -0800 @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary micro-benchmark correctness mode + * @run main URLMicroBenchmark iterations=1 size=8 warmup=0 + */ + +import static java.util.stream.Collectors.summingInt; + +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import java.net.URL; + + +public class URLMicroBenchmark { + abstract static class Job { + private final String name; + public Job(String name) { this.name = name; } + public String name() { return name; } + public abstract void work() throws Throwable; + } + + static String toExternalForm_jdk9(URL u) { + // pre-compute length of StringBuffer + int len = u.getProtocol().length() + 1; + if (u.getAuthority() != null && u.getAuthority().length() > 0) + len += 2 + u.getAuthority().length(); + if (u.getPath() != null) { + len += u.getPath().length(); + } + if (u.getQuery() != null) { + len += 1 + u.getQuery().length(); + } + if (u.getRef() != null) + len += 1 + u.getRef().length(); + + StringBuilder result = new StringBuilder(len); + result.append(u.getProtocol()); + result.append(":"); + if (u.getAuthority() != null && u.getAuthority().length() > 0) { + result.append("//"); + result.append(u.getAuthority()); + } + if (u.getPath() != null) { + result.append(u.getPath()); + } + if (u.getQuery() != null) { + result.append('?'); + result.append(u.getQuery()); + } + if (u.getRef() != null) { + result.append("#"); + result.append(u.getRef()); + } + return result.toString(); + } + + static String toExternalForm_concat_chars(URL u) { + String s; + return u.getProtocol() + + ':' + + (((s = u.getAuthority()) != null && s.length() > 0) + ? "//" + s : "") + + (((s = u.getPath()) != null) ? s : "") + + (((s = u.getQuery()) != null) ? '?' + s : "") + + (((s = u.getRef()) != null) ? '#' + s : ""); + } + + static String toExternalForm_concat_string(URL u) { + String s; + return u.getProtocol() + + ":" + + (((s = u.getAuthority()) != null && s.length() > 0) + ? "//" + s : "") + + (((s = u.getPath()) != null) ? s : "") + + (((s = u.getQuery()) != null) ? "?" + s : "") + + (((s = u.getRef()) != null) ? "#" + s : ""); + } + + static String toExternalForm_concat_incremental_string(URL u) { + String s; + String r = u.getProtocol() + ":"; + if ((s = u.getAuthority()) != null && s.length() > 0) + r += "//" + s; + if ((s = u.getPath()) != null) + r += s; + if ((s = u.getQuery()) != null) + r += "?" + s; + if ((s = u.getRef()) != null) + r += "#" + s; + return r; + } + + static String toExternalForm_concat_incremental_chars(URL u) { + String s; + String r = u.getProtocol() + ':'; + if ((s = u.getAuthority()) != null && s.length() > 0) + r = r + "//" + s; + if ((s = u.getPath()) != null) + r = r + s; + if ((s = u.getQuery()) != null) + r = r + '?' + s; + if ((s = u.getRef()) != null) + r = r + '#' + s; + return r; + } + + static String toExternalForm_StringBuilder_cache(URL u) { + final String protocol, authority, path, query, ref; + final int len = (protocol = u.getProtocol()).length() + 1 + + (((authority = u.getAuthority()) != null) ? 2 + authority.length() : 0) + + (((path = u.getPath()) != null) ? path.length() : 0) + + (((query = u.getQuery()) != null) ? 1 + query.length() : 0) + + (((ref = u.getRef()) != null) ? 1 + ref.length() : 0); + + StringBuilder result = new StringBuilder(len); + result.append(protocol).append(':'); + if (authority != null && authority.length() > 0) + result.append('/').append('/').append(authority); + if (path != null) + result.append(path); + if (query != null) + result.append('?').append(query); + if (ref != null) + result.append('#').append(ref); + return result.toString(); + } + + final int iterations; + final double warmupSeconds; + final long warmupNanos; + final Pattern filter; // select subset of Jobs to run + final boolean reverse; // reverse order of Jobs + final boolean shuffle; // randomize order of Jobs + + URLMicroBenchmark(String[] args) { + iterations = intArg(args, "iterations", 600_000); + warmupSeconds = doubleArg(args, "warmup", 7.0); + filter = patternArg(args, "filter"); + reverse = booleanArg(args, "reverse"); + shuffle = booleanArg(args, "shuffle"); + + warmupNanos = (long) (warmupSeconds * (1000L * 1000L * 1000L)); + } + + // --------------- GC finalization infrastructure --------------- + + /** No guarantees, but effective in practice. */ + static void forceFullGc() { + CountDownLatch finalizeDone = new CountDownLatch(1); + WeakReference ref = new WeakReference(new Object() { + protected void finalize() { finalizeDone.countDown(); }}); + try { + for (int i = 0; i < 10; i++) { + System.gc(); + if (finalizeDone.await(1L, TimeUnit.SECONDS) && ref.get() == null) { + System.runFinalization(); // try to pick up stragglers + return; + } + } + } catch (InterruptedException unexpected) { + throw new AssertionError("unexpected InterruptedException"); + } + throw new AssertionError("failed to do a \"full\" gc"); + } + + /** + * Runs each job for long enough that all the runtime compilers + * have had plenty of time to warm up, i.e. get around to + * compiling everything worth compiling. + * Returns array of average times per job per run. + */ + long[] time0(List jobs) throws Throwable { + final int size = jobs.size(); + long[] nanoss = new long[size]; + for (int i = 0; i < size; i++) { + if (warmupNanos > 0) forceFullGc(); + Job job = jobs.get(i); + long totalTime; + int runs = 0; + long startTime = System.nanoTime(); + do { job.work(); runs++; } + while ((totalTime = System.nanoTime() - startTime) < warmupNanos); + nanoss[i] = totalTime/runs; + } + return nanoss; + } + + void time(List jobs) throws Throwable { + if (warmupNanos > 0) time0(jobs); // Warm up run + final int size = jobs.size(); + final long[] nanoss = time0(jobs); // Real timing run + final long[] milliss = new long[size]; + final double[] ratios = new double[size]; + + final String nameHeader = "Method"; + final String millisHeader = "Millis"; + final String ratioHeader = "Ratio"; + + int nameWidth = nameHeader.length(); + int millisWidth = millisHeader.length(); + int ratioWidth = ratioHeader.length(); + + for (int i = 0; i < size; i++) { + nameWidth = Math.max(nameWidth, jobs.get(i).name().length()); + + milliss[i] = nanoss[i]/(1000L * 1000L); + millisWidth = Math.max(millisWidth, + String.format("%d", milliss[i]).length()); + + ratios[i] = (double) nanoss[i] / (double) nanoss[0]; + ratioWidth = Math.max(ratioWidth, + String.format("%.3f", ratios[i]).length()); + } + + String format = String.format("%%-%ds %%%dd %%%d.3f%%n", + nameWidth, millisWidth, ratioWidth); + String headerFormat = String.format("%%-%ds %%%ds %%%ds%%n", + nameWidth, millisWidth, ratioWidth); + System.out.printf(headerFormat, "Method", "Millis", "Ratio"); + + // Print out absolute and relative times, calibrated against first job + for (int i = 0; i < size; i++) + System.out.printf(format, jobs.get(i).name(), milliss[i], ratios[i]); + } + + private static String keywordValue(String[] args, String keyword) { + for (String arg : args) + if (arg.startsWith(keyword)) + return arg.substring(keyword.length() + 1); + return null; + } + + private static int intArg(String[] args, String keyword, int defaultValue) { + String val = keywordValue(args, keyword); + return (val == null) ? defaultValue : Integer.parseInt(val); + } + + private static double doubleArg(String[] args, String keyword, double defaultValue) { + String val = keywordValue(args, keyword); + return (val == null) ? defaultValue : Double.parseDouble(val); + } + + private static Pattern patternArg(String[] args, String keyword) { + String val = keywordValue(args, keyword); + return (val == null) ? null : Pattern.compile(val); + } + + private static boolean booleanArg(String[] args, String keyword) { + String val = keywordValue(args, keyword); + if (val == null || val.equals("false")) return false; + if (val.equals("true")) return true; + throw new IllegalArgumentException(val); + } + + private static List filter(Pattern filter, List jobs) { + if (filter == null) return jobs; + ArrayList newJobs = new ArrayList<>(); + for (Job job : jobs) + if (filter.matcher(job.name()).find()) + newJobs.add(job); + return newJobs; + } + + public static void main(String[] args) throws Throwable { + new URLMicroBenchmark(args).run(); + } + + void run() throws Throwable { + time(filter(filter, jobs())); + } + List jobs() throws Throwable { + final String[] urlStrings = new String[] { + "https://docs.oracle.com/javase/9/docs/api/java/net/URL.html", + "https://docs.oracle.com/javase/9/docs/api/java/net/URL.html#URL-java.lang.String-java.lang.String-int-java.lang.String-java.net.URLStreamHandler-", + }; + final URL[] urls = Arrays.stream(urlStrings) + .map(s -> { try { return new URL(s); } + catch (Throwable t) { throw new Error(t); }}) + .toArray(URL[]::new); + final String[] externalForms = new String[urls.length]; + return List.of( + new Job("StringBuilder jdk9") { + public void work() throws Throwable { + for (int i = 0; i < iterations; i++) + for (int j = 0; j < urls.length; j++) + externalForms[j] = toExternalForm_jdk9(urls[j]); + if (!Arrays.equals(urlStrings, externalForms)) throw new Error(); + }}, + new Job("concat_chars") { + public void work() throws Throwable { + for (int i = 0; i < iterations; i++) + for (int j = 0; j < urls.length; j++) + externalForms[j] = toExternalForm_concat_chars(urls[j]); + if (!Arrays.equals(urlStrings, externalForms)) throw new Error(); + }}, + new Job("concat_string") { + public void work() throws Throwable { + for (int i = 0; i < iterations; i++) + for (int j = 0; j < urls.length; j++) + externalForms[j] = toExternalForm_concat_string(urls[j]); + if (!Arrays.equals(urlStrings, externalForms)) throw new Error(); + }}, + new Job("concat_incremental_string") { + public void work() throws Throwable { + for (int i = 0; i < iterations; i++) + for (int j = 0; j < urls.length; j++) + externalForms[j] = toExternalForm_concat_incremental_string(urls[j]); + if (!Arrays.equals(urlStrings, externalForms)) throw new Error(); + }}, + new Job("concat_incremental_chars") { + public void work() throws Throwable { + for (int i = 0; i < iterations; i++) + for (int j = 0; j < urls.length; j++) + externalForms[j] = toExternalForm_concat_incremental_chars(urls[j]); + if (!Arrays.equals(urlStrings, externalForms)) throw new Error(); + }}, + new Job("StringBuilder_cache") { + public void work() throws Throwable { + for (int i = 0; i < iterations; i++) + for (int j = 0; j < urls.length; j++) + externalForms[j] = toExternalForm_StringBuilder_cache(urls[j]); + if (!Arrays.equals(urlStrings, externalForms)) throw new Error(); + }}); + } +}