/* * 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(); }}); } }