[NEW BUG] Reduce allocations from Class.toString() for primitives

Christoph Dreis christoph.dreis at freenet.de
Mon Apr 6 17:49:30 UTC 2020


Hi,

I just noticed an opportunity to optimize Class.toString() for primitive types.

I've written a small benchmark and ran it against JDK 15 vs a patched variant.

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {

    @State(Scope.Benchmark)
    public static class ThreadState {

        private Class<?> primitive = long.class;
        private Class<?> clazz = Long.class;
        private Class<?> interfaze = Map.class;

    }

    @Benchmark
    public String testPrimitive(ThreadState threadState) {
        return threadState.primitive.toString();
    }

    @Benchmark
    public String testClass(ThreadState threadState) {
        return threadState.clazz.toString();
    }

    @Benchmark
    public String testInterface(ThreadState threadState) {
        return threadState.interfaze.toString();
    }

}

This yields the following results:

Before
Benchmark                                                   Mode  Cnt     Score     Error   Units
MyBenchmark.testClass                                       avgt   10    32,268 ±   2,961   ns/op
MyBenchmark.testClass:·gc.alloc.rate                        avgt   10  3601,964 ± 336,786  MB/sec
MyBenchmark.testClass:·gc.alloc.rate.norm                   avgt   10   152,009 ±   0,001    B/op
MyBenchmark.testClass:·gc.count                             avgt   10   203,000            counts
MyBenchmark.testClass:·gc.time                              avgt   10   138,000                ms
MyBenchmark.testInterface                                   avgt   10    37,685 ±   3,728   ns/op
MyBenchmark.testInterface:·gc.alloc.rate                    avgt   10  3086,794 ± 309,983  MB/sec
MyBenchmark.testInterface:·gc.alloc.rate.norm               avgt   10   152,008 ±   0,001    B/op
MyBenchmark.testInterface:·gc.count                         avgt   10   160,000            counts
MyBenchmark.testInterface:·gc.time                          avgt   10   107,000                ms
MyBenchmark.testPrimitive                                   avgt   10    20,937 ±   2,668   ns/op
MyBenchmark.testPrimitive:·gc.alloc.rate                    avgt   10  3809,437 ± 470,140  MB/sec
MyBenchmark.testPrimitive:·gc.alloc.rate.norm               avgt   10   104,006 ±   0,001    B/op
MyBenchmark.testPrimitive:·gc.count                         avgt   10   215,000            counts
MyBenchmark.testPrimitive:·gc.time                          avgt   10   167,000                ms


After
Benchmark                                                   Mode  Cnt     Score     Error   Units
MyBenchmark.testClass                                       avgt   10    31,585 ±   5,365   ns/op
MyBenchmark.testClass:·gc.alloc.rate                        avgt   10  3704,714 ± 549,224  MB/sec
MyBenchmark.testClass:·gc.alloc.rate.norm                   avgt   10   152,008 ±   0,001    B/op
MyBenchmark.testClass:·gc.count                             avgt   10   191,000            counts
MyBenchmark.testClass:·gc.time                              avgt   10   139,000                ms
MyBenchmark.testInterface                                   avgt   10    34,534 ±   2,073   ns/op
MyBenchmark.testInterface:·gc.alloc.rate                    avgt   10  3358,401 ± 193,391  MB/sec
MyBenchmark.testInterface:·gc.alloc.rate.norm               avgt   10   152,008 ±   0,001    B/op
MyBenchmark.testInterface:·gc.count                         avgt   10   173,000            counts
MyBenchmark.testInterface:·gc.time                          avgt   10   131,000                ms
MyBenchmark.testPrimitive                                   avgt   10     2,829 ±   0,139   ns/op
MyBenchmark.testPrimitive:·gc.alloc.rate                    avgt   10    ≈ 10⁻⁴            MB/sec
MyBenchmark.testPrimitive:·gc.alloc.rate.norm               avgt   10    ≈ 10⁻⁶              B/op
MyBenchmark.testPrimitive:·gc.count                         avgt   10       ≈ 0            counts

I don't think the patched version is actually faster for classes & interfaces; I guess it's rather a draw for those.
Yet, for primitives we can see a 10x improvement due to the avoided string concats.

In case you think this is worthwhile I would need someone to sponsor this patch.
I would highly appreciate that. Let me know what you think

Cheers,
Christoph


===== PATCH =====
diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java
--- a/src/java.base/share/classes/java/lang/Class.java
+++ b/src/java.base/share/classes/java/lang/Class.java
@@ -196,8 +196,11 @@
      * @return a string representation of this {@code Class} object.
      */
     public String toString() {
-        return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
-            + getName();
+        String name = getName();
+        if (isPrimitive()) {
+               return name;
+        }
+        return (isInterface() ? "interface " : "class ") + name;
     }




More information about the core-libs-dev mailing list