Skip to content

Proposal: Spans for JNI Member lookups #11885

Description

@jonpryor

Currently, Java.Interop.JniPeerMembers member lookups are backed via dictionary lookup with string as the key-type, e.g.

https://github.com/xamarin/java.interop/blob/7dc270dbb83948b278bee38fc83bf9ae5cd42a7e/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs#L18
https://github.com/xamarin/java.interop/blob/7dc270dbb83948b278bee38fc83bf9ae5cd42a7e/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs#L25-L36

This works, and provides a reasonably "user-friendly" interface, but has hidden performance implications:

(Note: this still appears to contribute ~2ms to Xamarin.Android app startup, so it's not a huge source of performance implications, but it is A Thing™ to consider…)

These methods are usually invoked via generator-emitted code, such as:

namespace Android.App {
	public partial class Activity {
		public static unsafe long InstanceCount {
			[Register ("getInstanceCount", "()J", "")]
			get {
				const string __id = "getInstanceCount.()J";
				try {
					var __rm = _members.StaticMethods.InvokeInt64Method (__id, null);
					return __rm;
				} finally {
				}
			}
		}
	}
}

We could improve this by:

  1. Pre-compute a hashcode value.
  2. Use Span<T> types, which
  3. Use byte instead of char, and
  4. Contain embedded nulls

Thus, instead of:

const string __id = "getInstanceCount.()J";
var __rm = _members.StaticMethods.InvokeInt64Method (__id, null);

We would instead have generator emit:

static readonly int hash_getInstanceCount = JniPeerMembers.ComputeHash ("getInstanceCount.()J");ReadOnlySpan<byte> __id = new stackalloc {
    (byte) 'g',
    (byte) 'e',(byte) 't',
    (byte) 0,  // terminates `getInstanceCount`
    (byte) '.'
    (byte) '(',
    (byte) ')',
    (byte) 'J',
    (byte) 0, // terminates `()J`
};
var __rm = _members.StaticMethods.InvokeInt64Method (hash_getInstanceCount, __id, null);

JniEnvironment.StaticMethods.GetStaticMethodID() & co. could then be overloaded to take ReadOnlySpan<T> parameters (as overloads to the current string parameters).

TODO: verify that ReadOnlySpan<byte> as a P/Invoke parameter (1) works, and (2) passes the data as-is, no copy or marshaling involved.

Doing all this would remove the need to marshal anything: everything would already be UTF-8, because that's what generator's stackalloc would contain, so there's no System.String-to-const char* marshaling involved (as is currently required). This would also help with the embedded Dictionary<string, JniMethodInfo> lookup, which could now become a Dictionary<int, JniMethodInfo> lookup (as the hash is pre-computed).

On the downside, this would be a kind-of ABI break: binding assemblies using these new members couldn't be used on older Xamarin.Android SDKs/etc. (This is where multi-targeting solves everything.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    java-interopIssues migrated from dotnet/java-interop / relates to the Java.Interop subtreeneeds-triageIssues that need to be assigned.possibly-staleIssues that are potentially no longer relevant.proposal

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions