-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Port java-spaghetti-gen to cafebabe; cache method/field IDs; fix jclass memory leaks #5
Conversation
The automatic check has failed because of it doesn't use a nightly compiler: |
I have fixed the CI script and the check has passed. Note: the JNI global reference capacity on Android is usually 51200, and I'm merely keeping global references for used classes (there are no more than 20,000 classes in |
I would like to know if I am making any unacceptable change. (just ignore the post for this moment if you're busy) Fixed another bug of java
|
@@ -157,28 +157,37 @@ impl<'a> Field<'a> { | |||
out, | |||
"{indent}{attributes}pub fn {get}<'env>({env_param}) -> {rust_get_type} {{", | |||
)?; | |||
writeln!( | |||
out, | |||
"{indent} static __FIELD: ::std::sync::OnceLock<usize> = ::std::sync::OnceLock::new();" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why cast to usize and back?
The semantics of usize<->ptr casts are a bit tricky, .addr()
doesn't expose the provenance, so it can't be used to reconstruct the pointer with as
casts. For this to be sound you'd have to use as
casts in both directions, or use .expose_provenance()
, .with_exposed_provenance_mut()
(which are equivalent to as
)
If the reason for usize
is because the pointer isn't Send/Sync it'd be cleaner to make a newtype and unsafe impl Send/Sync
on it. This way the pointer can stay as a pointer all the time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the reason for
usize
is because the pointer isn't Send/Sync it'd be cleaner to make a newtype andunsafe impl Send/Sync
on it. This way the pointer can stay as a pointer all the time.
I have modified the code according to your suggestion. I tested the newly generated bindings and it works. Thank you.
I did another test to make sure these cached IDs can be used across threads. The problem is about the JNI specification itself not describing clearly that these IDs are thread-safe. But some other articles (as well as The Android JNI tips says: "Because there is a limit of one JavaVM per process, it's reasonable to store this data in a static local structure." (https://developer.android.com/training/articles/perf-jni#jclass,-jmethodid,-and-jfieldid) The word "local" used here is probably about the scope of the variable in C programs, but not about thread-local storage. Do you think so? |
yeah, it looks good to me. Everything seems to imply theyŕe global pointers. Thanks for the PR! |
Commit 1: Port java-spaghetti-gen to cafebabe
Introduced
cafebabe
as an alternative to the unmaintainedjreflection
crate. However, there are some workarounds for staktrace/cafebabe#52.Difference of execution speed (release build) cannot be realized.
The generated
bindings.rs
for the wholeandroid.jar
keeps the same, except these differences:java.flags
comment: SYNCRONIZED -> SYNCHRONIZEDbool%5B%5D
->boolean%5B%5D
(bool
was a bug)__jni_bindgen
namespace is removedCommit 2: Fix java-spaghetti-gen reference URL generation
Documentation URL comment generation: changed
%5B%5D
to[]
, because the android documentation website uses[]
directly in URLs for now, and links like https://developer.android.com/reference/android/icu/util/Currency.html#getName(java.util.Locale,%20int,%20boolean%5B%5D) cannot jump to the correct item.Commit 3: Cache method/field ID; fix jclass memory leaks
This is a significant performance improvement. Caching method/field IDs may encounter validity issues: https://mostlynerdless.de/blog/2023/07/17/jmethodids-in-profiling-a-tale-of-nightmares/; I try to avoid the problem by holding a global reference of the class object for each class being used.
Test case:
Before the change:
After the change:
Note: it would be
local reference table overflow (max=512)
on Android 6.0.Another change is possible: reduce
DeleteLocalRef
calls by pushing/popping local frames inVM::get_env
This seems like a minor optimization (jni-rs/jni-rs#560 (comment)). Being capable of making this change, I will not achieve it unless you really want me to do so. The disadvantage of this optimization is about the complexity of maintaining a local frame tracker (stack) and an overall local reference counter in the thread local storage:
VM::get_env
needs to push a "frame" into the tracker stack before executing the closure, and pop a "frame" from that stack after executing the closure, reducing the overall local ref counter by the usage counter for the local frame being popped.Env
wrapper created byVM::get_env
should keep the corresponding local frame depth (it cannot be transparent) internally, to prevent the use of outerenv
in an inner frame. In callback functions, theenv
pointer passed to the function may be wrapped by anunsafe
function maintaining the frame tracker just like whatVM::get_env
does.jobject
s coming from droppedLocal
s.Local
wrapper; every operation that creates aLocal
wrapper (probably includingfrom_raw
) needs to increase the frame usage counter of the stack top (as well as the overall usage counter), callEnsureLocalCapacity
to increase capacity estimation if needed; if the overall counter reaches certain limit, it may even pop somejobject
s from the "trash can" and callDeleteLocalRef
for them, then decrease both counters.Local
needs to store its corresponding frame depth too, because aLocal
created in an outer frame may be dropped in an inner frame: push it into the trash can of the correct level in the frame tracker.