On 14-Jun-20 18:28, Peter Levart wrote:
Hi,
I noticed that deserializing records (new preview Java feature in
JDK14 and JDK15) is slow compared to equivalent classical classes. I
created a JMH benchmark [1] to se how it compares (ran it on JDK14):
Benchmark Mode Cnt Score Error Units
RecordSerializationBench.deserializeClasses avgt 10 31.911
± 0.218 us/op
RecordSerializationBench.deserializeClasses:·gc.alloc.rate avgt
10 815.106 ± 5.545 MB/sec
RecordSerializationBench.deserializeClasses:·gc.alloc.rate.norm avgt
10 40921.903 ± 0.615 B/op
RecordSerializationBench.deserializeClasses:·gc.churn.G1_Eden_Space
avgt 10 839.522 ± 191.195 MB/sec
RecordSerializationBench.deserializeClasses:·gc.churn.G1_Eden_Space.norm
avgt 10 42153.661 ± 9682.799 B/op
RecordSerializationBench.deserializeClasses:·gc.churn.G1_Survivor_Space
avgt 10 0.117 ± 0.492 MB/sec
RecordSerializationBench.deserializeClasses:·gc.churn.G1_Survivor_Space.norm
avgt 10 5.835 ± 24.447 B/op
RecordSerializationBench.deserializeClasses:·gc.count avgt 10
21.000 counts
RecordSerializationBench.deserializeClasses:·gc.time avgt 10
17.000 ms
RecordSerializationBench.deserializeRecords avgt 10 531.333
± 3.094 us/op
RecordSerializationBench.deserializeRecords:·gc.alloc.rate avgt
10 346.511 ± 1.997 MB/sec
RecordSerializationBench.deserializeRecords:·gc.alloc.rate.norm avgt
10 289637.193 ± 6.894 B/op
RecordSerializationBench.deserializeRecords:·gc.churn.G1_Eden_Space
avgt 10 359.773 ± 191.116 MB/sec
RecordSerializationBench.deserializeRecords:·gc.churn.G1_Eden_Space.norm
avgt 10 300657.838 ± 159724.346 B/op
RecordSerializationBench.deserializeRecords:·gc.churn.G1_Survivor_Space
avgt 10 0.007 ± 0.012 MB/sec
RecordSerializationBench.deserializeRecords:·gc.churn.G1_Survivor_Space.norm
avgt 10 6.020 ± 9.910 B/op
RecordSerializationBench.deserializeRecords:·gc.count avgt 10
9.000 counts
RecordSerializationBench.deserializeRecords:·gc.time avgt 10
14.000 ms
...not only it is it about 16x slower, it also produces 7x garbage. I
checked the code and it is not very optimal. It matches the record
component names with object stream fields in O(n^2) way. It uses
method handles but binds arguments of canonical constructor each time
an instance of record is constructed. So I tried to optimize it [2]
and with that patch on top of JDK16 the benchmark produces the
following results:
Benchmark Mode Cnt Score Error Units
RecordSerializationBench.deserializeClasses avgt 10 31.049 ±
0.235 us/op
RecordSerializationBench.deserializeClasses:·gc.alloc.rate avgt
10 837.614 ± 6.326 MB/sec
RecordSerializationBench.deserializeClasses:·gc.alloc.rate.norm avgt
10 40921.931 ± 0.666 B/op
RecordSerializationBench.deserializeClasses:·gc.churn.G1_Eden_Space
avgt 10 867.743 ± 251.373 MB/sec
RecordSerializationBench.deserializeClasses:·gc.churn.G1_Eden_Space.norm
avgt 10 42405.532 ± 12403.301 B/op
RecordSerializationBench.deserializeClasses:·gc.churn.G1_Survivor_Space
avgt 10 0.126 ± 0.478 MB/sec
RecordSerializationBench.deserializeClasses:·gc.churn.G1_Survivor_Space.norm
avgt 10 6.113 ± 23.268 B/op
RecordSerializationBench.deserializeClasses:·gc.count avgt 10
22.000 counts
RecordSerializationBench.deserializeClasses:·gc.time avgt 10
19.000 ms
RecordSerializationBench.deserializeRecords avgt 10 33.588 ±
0.394 us/op
RecordSerializationBench.deserializeRecords:·gc.alloc.rate avgt
10 500.033 ± 5.871 MB/sec
RecordSerializationBench.deserializeRecords:·gc.alloc.rate.norm avgt
10 26425.293 ± 0.759 B/op
RecordSerializationBench.deserializeRecords:·gc.churn.G1_Eden_Space
avgt 10 512.772 ± 288.112 MB/sec
RecordSerializationBench.deserializeRecords:·gc.churn.G1_Eden_Space.norm
avgt 10 27090.499 ± 15175.280 B/op
RecordSerializationBench.deserializeRecords:·gc.churn.G1_Survivor_Space
avgt 10 0.134 ± 0.496 MB/sec
RecordSerializationBench.deserializeRecords:·gc.churn.G1_Survivor_Space.norm
avgt 10 7.128 ± 26.526 B/op
RecordSerializationBench.deserializeRecords:·gc.count avgt 10
13.000 counts
RecordSerializationBench.deserializeRecords:·gc.time avgt 10
17.000 ms
...so here the speed is comparable and it even produces less garbage.
I created an issue [3].
So WDYT? Since this is still a preview feature in JDK15, is it
possible to squeeze it into JDK15?
Regards, Peter
[1]
http://cr.openjdk.java.net/~plevart/jdk-dev/RecordsDeserialization/RecordSerializationBench.java
[2]
http://cr.openjdk.java.net/~plevart/jdk-dev/RecordsDeserialization/webrev.01/
[3] https://bugs.openjdk.java.net/browse/JDK-8247532
Small suggestion: RecordSupport.defaultValueExtractorFor could be
written as:
return MethodHandles.empty(MethodType.methodType(pClass,
byte[].class, Object[].class));
It could then be inlined.
- Johannes