Javet icon indicating copy to clipboard operation
Javet copied to clipboard

Java object call performance is poor

Open aiselp opened this issue 3 months ago • 7 comments

There are some performance issues when accessing Java object methods and properties using JavaScript. Testing shows that the following code runs about 20 times slower than in Rhino.

 // File is obtained via JavaProxyConverter 
let file = new java.io.File("/"); 
let i = 0; 
while (true) { 
  file.hashCode()
  i++; 
  if (i > 500 * 1000) break; 
} 

I suspect that much of the time is wasted searching for the corresponding method through Java reflection. Rhino pre-stores all the methods, properties, and other information for the Java class, avoiding repeated searches and matching for frequent method calls. Is it possible to store this information in the JavaScript environment in Java? Java assigns an ID to each method, and JavaScript can then find the corresponding method and call Java using the ID. This would significantly improve call speed.

aiselp avatar Sep 28 '25 14:09 aiselp

Thank you for raising this issue. When JavaProxyConverter was designed, performance was not the top priority. The slowness mostly comes from the JNI calls, I suppose.

Of course, there are rooms for caching simple calls. But, I doubt the performance improvement would not be noticeable and memory usage would slightly increase.

The general suggestion is: Do not use JavaProxyConverter in performance sensitive cases. You can always write your own converters for best performance.

caoccao avatar Sep 28 '25 15:09 caoccao

The impact of JNI calls may not be high. It is just a simple change in the calling method. The runtime is reduced by about 70%, but it is still twice that of Rhino.

 // File is obtained via JavaProxyConverter 
let file = new java.io.File("/"); 
let hashCode = file.hashCode
let i = 0; 
while (true) { 
  hashCode()
  i++; 
  if (i > 500 * 1000) break; 
} 

Regarding memory usage, it is not about caching every call of each instance, but only recording a copy of the class information corresponding to the instance. Memory usage does not increase with the number of objects, but with the number of classes.

aiselp avatar Sep 29 '25 04:09 aiselp

file.hashCode() => hashCode() saves the JNI calls by 2/3.

caoccao avatar Sep 29 '25 17:09 caoccao

Thanks for your answer. I'm planning to write a custom converter and need an efficient way to access Java from JavaScript. How should I do this? I tried V8ValueObject.bind, but it seems slower than JavaProxyConverter.

aiselp avatar Sep 30 '25 05:09 aiselp

I don't think you can create a better converter than JavaProxyConverter if you want it to let you call arbitrary Java API.

If your use case is limited, there will be room for a custom converter with better performance.

caoccao avatar Sep 30 '25 07:09 caoccao

file.hashCode() => hashCode() saves the JNI calls by 2/3.

According to what you said file.hashCode() will call JNI multiple times. If it is optimized to make only one JNI call, will it be much faster?

aiselp avatar Sep 30 '25 11:09 aiselp

The optimization process is as follows:

  1. Create a model describing the Java class in JavaScript or C++, including all parent class names, property types, method signatures, and other information.
  2. When calling a Java method from JavaScript, retrieve the class model for the Java object. If it doesn't exist, create and cache it.
  3. Analyze the method to be called from the model based on the parameters passed in by JavaScript.
  4. Finally, call the Java method through JNI using the pre-stored Java method ID.

aiselp avatar Sep 30 '25 11:09 aiselp