I was curious how much overhead there was in calling functions via JEP 389: Foreign Linker API. I have searched around but I can't seem to find that kind of info. I was taking a look to see if it is realistic to wrap a c++ 3d math library that uses SIMD stuff with it. For this case the overhead must be very low since the operations themselves take little time.
By my estimations on my machine it takes 10 nanoseconds to call. This is certainly very impressive but that does not work out when the target only takes nanoseconds to run!
I don't have a ton of Java experience so I was wondering if I am missing out on some configuration things or just something obvious. I'm just trying to wrap something up for Clojure.
This is (roughly) how I got my numbers:
rtm.cpp
extern "C" {
float inc_float(float f) {
return f + 1.0f;
}
}
Compiled with
clang -std=c++17 -O0 -shared -undefined dynamic_lookup -o librtm.so rtm.cpp
(also tested with -Ofast)
WrapperTest.java
import static jdk.incubator.foreign.CLinker.C_FLOAT;
import jdk.incubator.foreign.*;
import java.lang.invoke.*;
import java.nio.file.Path;
@Test public void testTonsOfSimpleCallsAlt() throws Throwable {
var path = Path.of("/path/to/librtm.so");
var libraryLookup = LibraryLookup.ofPath(path);
var incFloatHandle = CLinker.getInstance().downcallHandle(
libraryLookup.lookup("inc_float").get(),
MethodType.methodType(float.class, float.class),
FunctionDescriptor.of(C_FLOAT, C_FLOAT)
);
// warmup
for (var i = 0; i < 1024; ++i) {
float dummy = (float)incFloatHandle.invokeExact(1.0f);
}
long startTime = System.nanoTime();
float total = 1.0f;
int loops = 1024 * 1024 * 100;
int ops = 10;
for (var i = 0; i < loops; ++i) {
// The actual important call!
total = (float)incFloatHandle.invokeExact(total);
// nine more of the above line...
total = total - 10.0f;
}
long endTime = System.nanoTime();
float nanos = (float)(endTime - startTime);
System.out.println("Time taken is ms: " + (nanos / 1000000.0f));
System.out.println("Time taken per op in ns:" + (nanos / (float)loops / (float)ops));
}
JVM arguments
--add-modules jdk.incubator.foreign -Dforeign.restricted=permit
Java version
> java --version
openjdk 16 2021-03-16
OpenJDK Runtime Environment AdoptOpenJDK (build 16+36)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 16+36, mixed mode, sharing)
Looking in a profiler I am getting these sorts of call stacks around the call to the c++ function:
- WrapperTest.java:131 java.lang.invoke.LambdaForm$MH.0x0000000800227000.invokeExact_MT(Object, float, Object) 991 1
- LambdaForm$MH java.lang.invoke.LambdaForm$MH.0x0000000800229c00.invoke(Object, float) 991 1
- LambdaForm$MH java.lang.invoke.LambdaForm$MH.0x000000080021d800.invoke(Object, float, long) 991 1
I have just run this via the IntelliJ test runner.
So is the overhead 10ns per call (machine dependent blah blah) or is there a way to get this running faster?