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;
}