Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6586572

Class.isInstance should be as fast as array store for type checking purposes

XMLWordPrintable

    • generic
    • generic

      Some code, like Collections.checkedCollection, needs to type check
      objects for assignability to a class, most obviously using Class.isInstance.
      Unfortunately, C1 doesn't do as good a job optimizing such checks as C2,
      even though the apparently equivalent problem of detecting array store exceptions
      is done quite well by C1. The performance degradation is more than
      an order of magnitude. This result might tempt users to do type checking
      using the obscure "trick" of writing to a 1-element array instead of using the
      Class.isInstance API designed for this purpose, to get that order of magnitude
      speedup.

      Here's a microbenchmark:

      ----------------------------
      import java.util.*;

      public class TypeCheckMicroBenchmark {
          abstract static class Job {
      private final String name;
      Job(String name) { this.name = name; }
      String name() { return name; }
      abstract void work() throws Throwable;
          }

          private static void collectAllGarbage() {
      final java.util.concurrent.CountDownLatch drained
      = new java.util.concurrent.CountDownLatch(1);
      try {
      System.gc(); // enqueue finalizable objects
      new Object() { protected void finalize() {
      drained.countDown(); }};
      System.gc(); // enqueue detector
      drained.await(); // wait for finalizer queue to drain
      System.gc(); // cleanup finalized objects
      } catch (InterruptedException e) { throw new Error(e); }
          }

          /**
           * 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.
           */
          private static long[] time0(Job ... jobs) throws Throwable {
      final long warmupNanos = 10L * 1000L * 1000L * 1000L;
      long[] nanoss = new long[jobs.length];
      for (int i = 0; i < jobs.length; i++) {
      collectAllGarbage();
      long t0 = System.nanoTime();
      long t;
      int j = 0;
      do { jobs[i].work(); j++; }
      while ((t = System.nanoTime() - t0) < warmupNanos);
      nanoss[i] = t/j;
      }
      return nanoss;
          }

          private static void time(Job ... jobs) throws Throwable {

      long[] warmup = time0(jobs); // Warm up run
      long[] nanoss = time0(jobs); // Real timing run
      long[] milliss = new long[jobs.length];
      double[] ratios = new double[jobs.length];

      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 < jobs.length; i++) {
      nameWidth = Math.max(nameWidth, jobs[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 < jobs.length; i++)
      System.out.printf(format, jobs[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 java.util.regex.Pattern patternArg(String[] args,
      String keyword) {
      String val = keywordValue(args, keyword);
      return val == null ? null : java.util.regex.Pattern.compile(val);
          }

          private static Job[] filter(java.util.regex.Pattern filter,
      Job[] jobs) {
      if (filter == null) return jobs;
      Job[] newJobs = new Job[jobs.length];
      int n = 0;
      for (Job job : jobs)
      if (filter.matcher(job.name()).find())
      newJobs[n++] = job;
      // Arrays.copyOf not available in JDK 5
      Job[] ret = new Job[n];
      System.arraycopy(newJobs, 0, ret, 0, n);
      return ret;
          }

          /**
           * Usage: [iterations=N] [size=N] [filter=REGEXP]
           */
          public static void main(String[] args) throws Throwable {
      final int iterations = intArg(args, "iterations", 30000);
      final int size = intArg(args, "size", 1000);
      final java.util.regex.Pattern filter
      = patternArg(args, "filter");

      final List<Integer> list = new ArrayList<Integer>();
      final Random rnd = new Random();
      for (int i = 0; i < size; i++)
      list.add(rnd.nextInt());
      final Class klazz = Integer.class;

      final Job[] jobs = {
      new Job("toArray(T[])") { void work() {
      Object[] a = new Integer[0];
      for (int i = 0; i < iterations; i++) {
      try { list.toArray(a); }
      catch (ArrayStoreException ase) {
      throw new ClassCastException(); }}}},
      new Job("isInstance") { void work() {
      for (int i = 0; i < iterations; i++) {
      for (Object x : list.toArray())
      if (! (x != null && klazz.isInstance(x)))
      throw new ClassCastException(); }}},
      new Job("Class.cast") { void work() {
      for (int i = 0; i < iterations; i++) {
      for (Object x : list.toArray())
      klazz.cast(x); }}},
      new Job("write into array") { void work() {
      Object[] a = new Integer[1];
      for (int i = 0; i < iterations; i++) {
      for (Object x : list.toArray()) {
      try { a[0] = x; }
      catch (ArrayStoreException _) {
      throw new ClassCastException(); }}}}},
      new Job("write into dynamic array") { void work() {
      for (int i = 0; i < iterations; i++) {
      for (Object x : list.toArray()) {
      Object[] a = (Object[])
      java.lang.reflect.Array.newInstance(klazz, 1);
      try { a[0] = x; }
      catch (ArrayStoreException _) {
      throw new ClassCastException(); }}}}}
      };

      time(filter(filter, jobs));
          }
      }
      ----------------------------

      Results on sparc:

       $ repeat 5 for f in -client -server; do jver -v 7 jr -dsa -da $f TypeCheckMicroBenchmark.java; done
      Using JDK /java/re/jdk/7/promoted/latest/binaries/solaris-sparcv9
      ==> javac -Xlint:all TypeCheckMicroBenchmark.java
      ==> java -dsa -da -client TypeCheckMicroBenchmark
      Method Millis Ratio
      toArray(T[]) 609 1.000
      isInstance 12401 20.341
      Class.cast 17820 29.228
      write into array 1032 1.693
      write into dynamic array 34970 57.357
      Using JDK /java/re/jdk/7/promoted/latest/binaries/solaris-sparcv9
      ==> javac -Xlint:all TypeCheckMicroBenchmark.java
      ==> java -dsa -da -server TypeCheckMicroBenchmark
      Method Millis Ratio
      toArray(T[]) 634 1.000
      isInstance 816 1.288
      Class.cast 842 1.329
      write into array 787 1.243
      write into dynamic array 1428 2.253
      ....

      Results on x86:

      repeat 5 for f in -client -server; do jver -v 7 jr -dsa -da $f TypeCheckMicroBenchmark.java; done
      Using JDK /java/re/jdk/7/promoted/latest/binaries/solaris-amd64
      ==> javac -Xlint:all TypeCheckMicroBenchmark.java
      ==> java -dsa -da -client TypeCheckMicroBenchmark
      Method Millis Ratio
      toArray(T[]) 186 1.000
      isInstance 2278 12.204
      Class.cast 2620 14.030
      write into array 230 1.235
      write into dynamic array 8930 47.822
      Using JDK /java/re/jdk/7/promoted/latest/binaries/solaris-amd64
      ==> javac -Xlint:all TypeCheckMicroBenchmark.java
      ==> java -dsa -da -server TypeCheckMicroBenchmark
      Method Millis Ratio
      toArray(T[]) 154 1.000
      isInstance 161 1.045
      Class.cast 168 1.089
      write into array 171 1.109
      write into dynamic array 422 2.738

            Unassigned Unassigned
            martin Martin Buchholz
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Imported:
              Indexed: