Skip to content

Proposal: Spans for JNI Member lookups #950

Open
@jonpryor

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-interopRuntime bridge between .NET and JavaproposalIssue raised for discussion, we do not even know if the change would be desirable yet

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions