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

Claes Redestad claes.redestad at oracle.com
Mon Apr 6 18:31:47 UTC 2020


Hi Christoph,

while the patch seems ok, I fail to think of a scenario where
performance of Class.toString() might be critical. Also it seems this
code is subject to change via valhalla[1]. So I think I'll say no, for
once. :-)

Thanks!

/Claes

[1] 
https://github.com/openjdk/valhalla/blob/lworld/src/java.base/share/classes/java/lang/Class.java#L197

On 2020-04-06 19:49, Christoph Dreis wrote:
> 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