Nikolay Izhikov created IGNITE-29109:
----------------------------------------

             Summary: TreeMap/TreeSet class descriptor is rebuilt on every 
binary write since 2.18.0 (regression of IGNITE-26061)
                 Key: IGNITE-29109
                 URL: https://issues.apache.org/jira/browse/IGNITE-29109
             Project: Ignite
          Issue Type: Bug
            Reporter: Nikolay Izhikov


h3. Summary

Since 2.18.0 every binary write of a {{java.util.TreeMap}} or 
{{java.util.TreeSet}} rebuilds its {{BinaryClassDescriptor}} by reflection. An 
empty {{TreeMap}} costs ~25x more to marshal than in 2.17.0 (~6 µs vs ~0.26 
µs); {{cache.put}} is ~10x slower and {{cache.get}} of a {{TreeMap}} value ~9x 
slower. {{HashMap}} and {{ArrayList}} are not affected. The compute closure 
wrappers {{GridClosureProcessor.C1/C1MLA/C2/C2MLA/C4/C4MLA}} take the same slow 
path. Reported on the dev list by a user; reproducer attached.

h3. Measurements

Reporter's reproducer (attached), one embedded server node, single thread, 
median of 5 rounds of 20 000 ops after warm-up, JDK 17, 12 CPUs, ns/op:

||Field value||2.17.0 toBinary||2.18.0 toBinary||2.17.0 put||2.18.0 put||2.17.0 
get||2.18.0 get||
|empty HashMap|395|521|1602|2669|1142|1673|
|empty ArrayList|289|292|865|957|472|516|
|empty TreeMap|261|*6221*|985|*9416*|532|*4728*|
|empty TreeSet|364|*5722*|990|*8563*|587|556|
|TreeMap, 12 entries|1098|*6956*|1873|*10208*|1139|*5335*|

h3. Root cause

Introduced by IGNITE-26061 (commit 06a557b2e4, PR #12230).

# {{BinaryContext}} constructor now calls {{registerBinarilizableSystemClass}} 
for {{BinaryTreeMap}}, {{TreeMap}} and {{TreeSet}}; 
{{GridClosureProcessor.onBinaryContextCreated}} does the same for the six 
closure wrappers.
# {{registerBinarilizableSystemClass}} puts {{systemClassDescriptor(...)}} into 
{{descByCls}}. That descriptor is created with {{registered = false}}.
# On every write {{registerClass}} sees {{!desc.registered()}} and calls 
{{registerDescriptor}}, which builds a registered copy with 
{{desc.makeRegistered()}} (full {{BinaryClassDescriptor}} constructor with 
reflective look-ups) and offers it with {{descByCls.putIfAbsent(...)}}.
# The unregistered entry is already present, so {{putIfAbsent}} returns it and 
the registered copy is discarded. The next write repeats the work.

Before IGNITE-26061 these classes were listed by name in 
{{BINARYLIZABLE_SYS_CLSS}} and nothing was in {{descByCls}} before the first 
write, so the first {{putIfAbsent}} cached the registered descriptor.

h3. Fix

Cache the descriptor in the registered state:

{code:java}
public void registerBinarilizableSystemClass(Class<?> cls) {
    String clsName = cls.getName();

    descByCls.put(cls, systemClassDescriptor(cls, clsName, new 
BinaryReflectiveSerializer()).makeRegistered());
}
{code}

With the fix the reproducer shows empty {{TreeMap}} at 453 ns toBinary / 1119 
ns put / 580 ns get, i.e. back at 2.17.0 level. A regression test is added to 
{{BinaryContextPredefinedTypesTest}} asserting that the cached descriptors of 
these system classes are registered and that {{registerClass}} returns the 
cached instance.

h3. Reproducer

{{treemap-descriptor-repro.zip}} (attached): {{mvn -q compile exec:exec}} runs 
against 2.18.0, {{-Dignite.version=2.17.0}} for comparison. See its README.md.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to