Increase concurrency in NativeMappedConverter using reentrant lock#1685
Increase concurrency in NativeMappedConverter using reentrant lock#1685brettwooldridge wants to merge 1 commit intojava-native-access:masterfrom
Conversation
|
How is the performance evaluated? This can only be better than the synchronized block when the code path is indeed often enough hit in a contended situation. My gut feeling would be, that the overhead of the lock trumps the benefit the potential(!) parallel invocation brings. |
|
This one would be hard to measure empirically, but only because of the number of callsites to Any time a If constructing The Structure case is just one of many callers to EDIT: I'll generate a JMH test harness. |
JMH ResultsEpyc 7402 Proxmox VM 16 vCoreThis was an extremely long run, approximately 40 minutes, threads 2x vCore count. JMH Harnesspackage eu.doppelhelix.jna.jmh;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
import com.sun.jna.Structure;
public class MyBenchmark {
public static class MyStruct extends Structure {
public byte boolValue; // Java boolean as native byte (0=false, 1=true)
public byte byteValue; // Java byte
public short shortValue; // Java short
public char charValue; // Java char (may map to C wchar_t)
public int intValue; // Java int
public long longValue; // Java long
public float floatValue; // Java float
public double doubleValue; // Java double
public String stringValue; // JNA auto-converts to const char * or wchar_t *
@Override
protected java.util.List<String> getFieldOrder() {
return java.util.Arrays.asList(
"boolValue",
"byteValue",
"shortValue",
"charValue",
"intValue",
"longValue",
"floatValue",
"doubleValue",
"stringValue"
);
}
}
@Benchmark
public void testMethod(Blackhole blackhole) {
MyStruct struct = new MyStruct();
blackhole.consume(struct);
}
} |
|
Thank you. The performance difference is in the range I expected. The observed speedup is well within the error range. I have a bad feeling integrating this given the recent fallouts from performance improvements. The new code path introduces a race: return converters.computeIfAbsent(cls, (clz) -> {
NativeMappedConverter nmc = new NativeMappedConverter(cls);
return new SoftReference<>(nmc);
}).get();Rewritten this is: var converterRef = converters.computeIfAbsent(cls, (clz) -> {
NativeMappedConverter nmc = new NativeMappedConverter(cls);
return new SoftReference<>(nmc);
});
return converterRef.get();What could happen now is, that GC collects the instance referenced by |
Fairly simple refactor removing synchronized block and replacing with a reentrant read-write lock, increasing concurrency in the primary (exists in map) path which allows multiple reader threads concurrent access.