[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