Open jonpryor opened 3 months ago
There isn't much point in continuing this effort, other than to show the idea and -- more importantly -- how it performs; see 840f04a4f4d7375a44f3a935901429c24f92c8ae. Trying to use JniMemberInfoLookup
for method invocations results in an order of magnitude performance degradation. It's non-viable.
What's here uses JniMemberInfoLookup
alongside ReadOnlySpan<JniArgumentValue>
. Locally, on the thought the performance loss was due to ReadOnlySpan<JniArgumentValue>
, I changed it all to instead be JniArgumentValue*
(which is what the other e.g. Invoke*Method()
methods use). This didn't help much: performance was still roughly an order of magnitude worse, e.g.
Method Lookup + Invoke Timing:
Traditional: 00:00:00.0135348
No caching: 00:00:00.0145592
Dict w/ lock: 00:00:00.0132410
ConcurrentDict: 00:00:00.0165738
JniPeerMembers: 00:00:00.0150361
JPM+Lookup: 00:00:00.0145659
(I)I virtual+traditional: 00:00:00.0000447
(I)I virtual+JniPeerMembers: 00:00:00.0000439
(I)I virtual+JPM+Lookup: 00:00:00.0004603
0.0000439 to 0.0004603 is a 10.4x increase in time: an order of magnitude.
Without pulling out a profiler, my guess is that this is due to the use of u8
strings, or due to code size increase. Timing_ToString_JniPeerMembers()
is 24 bytes, while Timing_ToString_JniPeerMembers_Lookup()
is more than double that, at 55 bytes.
Context: c6c487b62dab4ffec45e61b09dd43afc89898caf Context: 312fbf439ed874bb5f4f25ee6d2c9a2b3c2f5a8b Context: 2197579478152fbc815eb15195977f808cd6bde4 Context: https://github.com/xamarin/xamarin-android/issues/7276
There is a desire to remove the "marshal-ilgen" component from .NET Android, which is responsible for all non-blittable type marshaling within P/Invoke (and related) invocations.
The largest source of such non-blittable parameter marshaling was with string marshaling:
JNIEnv::GetFieldID()
was "wrapped" byjava_interop_jnienv_get_field_id
:which was P/Invoked within
JniEnvironment.g.cs
:and
string
parameter marshaling is not blittable.Turns out™ that this particular usage of non-blittable parameter marshaling was fixed and rendered moot by:
JNIEnv
invocationsThat said, this code path felt slightly less than ideal: the "top-level abstraction" for member lookups is an "encoded member", a string containing the name of the member, a
.
, and the JNI signature of the member, e.g.:The "encoded member" would need to be split on
.
, and with c6c487b6 the name and signature would be separately passed toMarshal.StringToCoTaskMemUTF8()
, which performs a memory allocation and converts the UTF-16 string to UTF-8.Meanwhile, C# 11 introduced UTF-8 string literals, which allows the compiler to deal with UTF-8 conversion and memory allocation.
Enter `JniMemberInfoLookup``:
JniMemberInfoLookup
removes the need to callMarshal.StringToCoTaskMemUTF8()
entirely, at the cost of a more complicated member invocation:Is It Worth It™? Maybe; see the new
JniFieldLookupTiming.FieldLookupTiming()
test, which allocates a newJniPeerMembers
instance and invokemembers.InstanceFields.GetFieldInfo(string)
andmembers.InstanceFields.GetFieldInfo(JniMemberInfoLookup)
. (A newJniPeerMembers
instance is required becauseGetFieldInfo()
caches the field lookup.) UsingJniMemberInfoLookup
is about 4% faster.I'm not sure if this is actually worth it, especially as this will imply an increase in code size.
TODO:
Update
JniPeerMembers.*.cs
to useJniMemberInfoLookup
, so that e.g. the above_members.InstanceFields.GetBooleanValue()
overload exists.generator
changes to useJniMemberInfoLookup