Skip to content

Commit

Permalink
[Java.Interop, Java.Interop.Dynamic] Array Performance Investigation
Browse files Browse the repository at this point in the history
Remember commit 775b34f?

> Con: OMFG IS THIS SLOW.
>
> Like, wow, incredibly slow, like ~11s to lookup the names and
> parameter types of all methods on java.util.Arrays.

So... WTF is going on there?

Object identity is what's going on there.

For "sane"/"correct"/"normal" semantics, JavaObjectArray<T> follows
object identity semantics, e.g. JavaVM.GetObject() will always return
the same (registered) wrapper for Java handle that refers to the
"same" Java instance, as per JavaVM.RegisteredInstances.

Supporting Object identity is...not fast. See the new
JavaArrayTiming.ObjectArrayEnumerationTiming() and
MiscRuntimeTiming.ObjectCreationTiming() tests.

ObjectArrayEnumerationTiming() uses Java reflection to grab all of the
methods of java.util.Arrays. The stunning output?

	# methodHandles(JavaObjectArray<JavaObject>) creation timing: 00:00:00.6106453 Count=114
	# methodHandles(JavaVM.GetObject) creation timing: 00:00:00.5959359 Count=114
	# methodHandles(JavaObject[]) creation timing: 00:00:00.0890776 Count=114
	# methodHandles(JniGlobalReference) creation timing: 00:00:00.1536897 Count=114

Given a JNI handle to the java.lang.reflect.Method[] returned by
Class.getMethods(), the above shows four different ways of reading
that Method[] and turning it into a List<Something>.

The JavaObjectArray<JavaObject> implementation takes 0.61 *seconds*
just to grab each element from the JavaObjectArray<JavaObject> and
copy it into a List<JavaObject>. For 115 items.

Which is *deplorable*.

Why is it so bad? Next is an implementation which grabs the JNI handle
for each element in the array and passes it through
JavaVM.GetObject<T>() before adding to a List<JavaObject>.
Note that it's VERY CLOSE in time to the JavaObjectArray<T> version.

The methodHandles(JavaObject[]) version grabs each element from the
array, then immediately constructs a new JavaObject instance around
it. NO OBJECT IDENTITY is involved; aliases can (will) be created, and
that's OKAY (here). Note that we drop from ~0.6s to ~0.089s --
nearly 7x faster, simply by skipping object identity.

The final methodHandles(JniGlobalReference) is the same-but-different
as methodHandles(JavaObject[]): instead of a new JavaObject, it
greates a new JNI Global Reference. (Why a GREF? Futureproof!) It's
slower than JavaObject (must be Local references vs. Global refs), but
still much faster than our object identity infrastructure.

TL;DR: Object identity is problematic.

There is an issue to attempt to address it:

	#4

...but we're not fixing that here.

The ObjectCreationTiming() tests are a different take on the above
results, comparing the timing of JNIEnv::AllocObject(),
JNIEnv::NewObject(), `new JavaObject()`, and
JavaVM.GetObject<JavaObject>():

	## ObjectCreationTiming Timing:
	Total=00:00:35.1368016
	AllocObject=00:00:02.8751501
	NewObject=00:00:02.8724492
	`new JavaObject()`=00:00:06.0586476
	JavaVM.GetObject()=00:00:14.5206758

This somewhat agrees with the previous discussion:
JavaVM.GetObject<T>() (object identity) is slower than direct object
creation.

With that in mind, we can turn our attention back to
Java.Interop.Dynamic and its terrible performance by replacing it's
JavaObjectArray<JavaObject> use with low-level array access.

This in turn necessitates making the JniEnvironment.Arrays type public
(previously `internal`) so that Java.Interop.Dynamic can skip/bypass
the object identity semantics that JavaObjectArray<T> enforces.
This improves things from taking ~11s to lookup the names to taking
~4s to run the entire Java.Interop.Dynamic-Tests assembly.

	Total time           : 3.9523533 seconds

This is much more reasonable. (Perhaps not perfect, but reasonable.)

While investigating, fix a variety of other minor issues:

  * Fix JavaObjectArray<T> so that it doesn't needlessly re-access the
    Length property all the damn time, which SHOULD reduce JNI calls
    and thus improve performance.

  * Fix JavaVM.CreateWrapperType() to not create so many JniType
    instances. Investigation was suggesting that the JniType creation
    and disposal was adding a bit of overhead, so avoiding the JniType
    abstraction provides *some* (small) speedup.

  * Cleanup JniType.Name (commit 9f380eb), moving its logic into
    JniEnvironment.Types.GetJniTypeNameFromClass(). This further
    increases consistency.
  • Loading branch information
jonpryor committed Sep 26, 2015
1 parent 8f7d339 commit 46b7aee
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 60 deletions.
45 changes: 24 additions & 21 deletions src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,23 +114,26 @@ void LookupMethods ()

StaticMethods = new Dictionary<string, List<JavaMethodInvokeInfo>> ();

using (var methods = new JavaObjectArray<JavaObject> (Class_getMethods.CallVirtualObjectMethod (members.JniPeerType.SafeHandle), JniHandleOwnership.Transfer)) {
foreach (var method in methods) {
var s = Member_getModifiers.CallVirtualInt32Method (method.SafeHandle);
using (var methods = Class_getMethods.CallVirtualObjectMethod (members.JniPeerType.SafeHandle)) {
int len = JniEnvironment.Arrays.GetArrayLength (methods);
for (int i = 0; i < len; ++i) {
var method = JniEnvironment.Arrays.GetObjectArrayElement (methods, i);
var s = Member_getModifiers.CallVirtualInt32Method (method);
if ((s & JavaModifiers.Static) != JavaModifiers.Static) {
method.Dispose ();
continue;
}

var name = JniEnvironment.Strings.ToString (Method_getName.CallVirtualObjectMethod (method.SafeHandle), JniHandleOwnership.Transfer);
var name = JniEnvironment.Strings.ToString (Method_getName.CallVirtualObjectMethod (method), JniHandleOwnership.Transfer);

List<JavaMethodInvokeInfo> overloads;
if (!StaticMethods.TryGetValue (name, out overloads))
StaticMethods.Add (name, overloads = new List<JavaMethodInvokeInfo> ());

var rt = new JniType (Method_getReturnType.CallVirtualObjectMethod (method.SafeHandle), JniHandleOwnership.Transfer);
var m = new JavaMethodInvokeInfo (name, true, rt, method);
var rt = new JniType (Method_getReturnType.CallVirtualObjectMethod (method), JniHandleOwnership.Transfer);
var m = new JavaMethodInvokeInfo (name, true, rt, method);
overloads.Add (m);
method.Dispose ();
}
}
}
Expand Down Expand Up @@ -356,18 +359,19 @@ sealed class JavaMethodInvokeInfo : IDisposable {

public string Name;
public JniType ReturnType;
public List<JniType> Arguments;
public bool IsStatic;
public JavaObject Method;

public List<JniGlobalReference> Arguments;
public JniGlobalReference Method;

public string Signature;

public JavaMethodInvokeInfo (string name, bool isStatic, JniType returnType, JavaObject method)
public JavaMethodInvokeInfo (string name, bool isStatic, JniType returnType, JniReferenceSafeHandle method)
{
Name = name;
IsStatic = isStatic;
ReturnType = returnType;
Method = method;
Method = method.NewGlobalRef ();
}

public void Dispose ()
Expand All @@ -383,8 +387,6 @@ public void Dispose ()
return;

for (int i = 0; i < Arguments.Count; ++i) {
if (Arguments [i] == null)
continue;
Arguments [i].Dispose ();
Arguments [i] = null;
}
Expand All @@ -400,13 +402,14 @@ public void LookupArguments ()
var sb = new StringBuilder ();
sb.Append (Name).Append ("\u0000").Append ("(");

Arguments = new List<JniType> ();
using (var methodParams = new JavaObjectArray<JavaObject> (DynamicJavaClass.Method_getParameterTypes.CallVirtualObjectMethod (Method.SafeHandle), JniHandleOwnership.Transfer)) {
foreach (var p in methodParams) {
var pt = new JniType (p.SafeHandle, JniHandleOwnership.DoNotTransfer);
Arguments.Add (pt);
sb.Append (vm.GetJniTypeInfoForJniTypeReference (pt.Name).JniTypeReference);
p.Dispose ();
using (var methodParams = DynamicJavaClass.Method_getParameterTypes.CallVirtualObjectMethod (Method)) {
int len = JniEnvironment.Arrays.GetArrayLength (methodParams);
Arguments = new List<JniGlobalReference> (len);
for (int i = 0; i < len; ++i) {
using (var p = JniEnvironment.Arrays.GetObjectArrayElement (methodParams, i)) {
sb.Append (JniEnvironment.Types.GetJniTypeNameFromClass (p));
Arguments.Add (p.NewGlobalRef ());
}
}
}
sb.Append (")").Append (vm.GetJniTypeInfoForJniTypeReference (ReturnType.Name).JniTypeReference);
Expand All @@ -425,10 +428,10 @@ public bool CompatibleWith (List<JniType> args, DynamicMetaObject[] dargs)
for (int i = 0; i < Arguments.Count; ++i) {
if (args [i] == null) {
// Builtin type -- JNIEnv.FindClass("I") throws!
if (Arguments [i].Name != vm.GetJniTypeInfoForType (dargs [i].LimitType).JniTypeReference)
if (JniEnvironment.Types.GetJniTypeNameFromClass (Arguments [i]) != vm.GetJniTypeInfoForType (dargs [i].LimitType).JniTypeReference)
return false;
}
else if (!Arguments [i].IsAssignableFrom (args [i]))
else if (!JniEnvironment.Types.IsAssignableFrom (Arguments [i], args [i].SafeHandle))
return false;
}
return true;
Expand Down
3 changes: 2 additions & 1 deletion src/Java.Interop/Java.Interop/JavaArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public T[] ToArray ()

public virtual IEnumerator<T> GetEnumerator ()
{
for (int i = 0; i < Length; ++i)
int len = Length;
for (int i = 0; i < len; ++i)
yield return this [i];
}

Expand Down
39 changes: 30 additions & 9 deletions src/Java.Interop/Java.Interop/JavaObjectArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public JavaObjectArray (IList<T> value)
: this (CheckLength (value))
{
for (int i = 0; i < value.Count; ++i)
this [i] = value [i];
SetElementAt (i, value [i]);
}

public JavaObjectArray (IEnumerable<T> value)
Expand All @@ -45,29 +45,50 @@ public override T this [int index] {
get {
if (index < 0 || index >= Length)
throw new ArgumentOutOfRangeException ("index", "index < 0 || index >= Length");
var lref = JniEnvironment.Arrays.GetObjectArrayElement (SafeHandle, index);
return JniMarshal.GetValue<T> (lref, JniHandleOwnership.Transfer);
return GetElementAt (index);
}
set {
if (index < 0 || index >= Length)
throw new ArgumentOutOfRangeException ("index", "index < 0 || index >= Length");
using (var h = JniMarshal.CreateLocalRef (value))
JniEnvironment.Arrays.SetObjectArrayElement (SafeHandle, index, h);
SetElementAt (index, value);
}
}

T GetElementAt (int index)
{
var lref = JniEnvironment.Arrays.GetObjectArrayElement (SafeHandle, index);
return JniMarshal.GetValue<T> (lref, JniHandleOwnership.Transfer);
}

void SetElementAt (int index, T value)
{
using (var h = JniMarshal.CreateLocalRef (value))
JniEnvironment.Arrays.SetObjectArrayElement (SafeHandle, index, h);
}

public override IEnumerator<T> GetEnumerator ()
{
int len = Length;
for (int i = 0; i < len; ++i) {
yield return GetElementAt (i);
}
}

public override void Clear ()
{
int len = Length;
for (int i = 0; i < len; i++)
this [i] = default (T);
using (var v = JniMarshal.CreateLocalRef (default (T))) {
for (int i = 0; i < len; i++) {
JniEnvironment.Arrays.SetObjectArrayElement (SafeHandle, i, v);
}
}
}

public override int IndexOf (T item)
{
int len = Length;
for (int i = 0; i < len; i++) {
var at = this [i];
var at = GetElementAt (i);
try {
if (EqualityComparer<T>.Default.Equals (item, at) || JniMarshal.RecursiveEquals (item, at))
return i;
Expand All @@ -92,7 +113,7 @@ internal override void CopyToList (IList<T> list, int index)
{
int len = Length;
for (int i = 0; i < len; i++) {
var item = this [i];
var item = GetElementAt (i);
list [index + i] = item;
if (forMarshalCollection) {
var d = item as IJavaObject;
Expand Down
23 changes: 16 additions & 7 deletions src/Java.Interop/Java.Interop/JavaVM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -496,9 +496,11 @@ protected virtual IJavaObject CreateObjectWrapper (JniReferenceSafeHandle handle
return (IJavaObject)Activator.CreateInstance (targetType, handle, transfer);
}

Type GetWrapperType (JniReferenceSafeHandle handle)
Type GetWrapperType (JniReferenceSafeHandle instance)
{
var jniTypeName = handle.GetJniTypeName ();
var klass = JniEnvironment.Types.GetObjectClass (instance);
var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass);

Type type = null;
while (jniTypeName != null) {
type = GetTypeForJniTypeRefererence (jniTypeName);
Expand All @@ -512,15 +514,22 @@ from c in type.GetConstructors (bindingFlags)
where p [0].ParameterType == typeof(JniReferenceSafeHandle) &&
p [1].ParameterType == typeof(JniHandleOwnership)
select c;
if (ctors.Any ())
if (ctors.Any ()) {
klass.Dispose ();
return type;
}
}

using (var jniType = new JniType (jniTypeName))
using (var super = jniType.GetSuperclass ()) {
jniTypeName = super == null ? null : super.Name;
}
var super = JniEnvironment.Types.GetSuperclass (klass);
jniTypeName = (super == null || super.IsClosed || super.IsInvalid)
? null
: JniEnvironment.Types.GetJniTypeNameFromClass (super);

klass.Dispose ();
klass = super;
}
if (klass != null)
klass.Dispose ();
return null;
}

Expand Down
27 changes: 25 additions & 2 deletions src/Java.Interop/Java.Interop/JniEnvironment.Types.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Java.Interop
{
partial class JniEnvironment {
static partial class Types {

readonly static KeyValuePair<string, string>[] BuiltinMappings = new KeyValuePair<string, string>[] {
new KeyValuePair<string, string>("byte", "B"),
new KeyValuePair<string, string>("boolean", "Z"),
new KeyValuePair<string, string>("char", "C"),
new KeyValuePair<string, string>("double", "D"),
new KeyValuePair<string, string>("float", "F"),
new KeyValuePair<string, string>("int", "I"),
new KeyValuePair<string, string>("long", "J"),
new KeyValuePair<string, string>("short", "S"),
new KeyValuePair<string, string>("void", "V"),
};


public static JniType GetTypeFromInstance (JniReferenceSafeHandle value)
{
var lref = JniEnvironment.Types.GetObjectClass (value);
Expand All @@ -17,13 +31,22 @@ public static JniType GetTypeFromInstance (JniReferenceSafeHandle value)
public static string GetJniTypeNameFromInstance (JniReferenceSafeHandle handle)
{
using (var c = GetObjectClass (handle)) {
var s = JniEnvironment.Current.Class_getName.CallVirtualObjectMethod (c);
return JavaClassToJniType (Strings.ToString (s, JniHandleOwnership.Transfer));
return GetJniTypeNameFromClass (c);
}
}

public static string GetJniTypeNameFromClass (JniReferenceSafeHandle handle)
{
var s = JniEnvironment.Current.Class_getName.CallVirtualObjectMethod (handle);
return JavaClassToJniType (Strings.ToString (s, JniHandleOwnership.Transfer));
}

static string JavaClassToJniType (string value)
{
for (int i = 0; i < BuiltinMappings.Length; ++i) {
if (value == BuiltinMappings [i].Key)
return BuiltinMappings [i].Value;
}
return value.Replace ('.', '/');
}
}
Expand Down
21 changes: 1 addition & 20 deletions src/Java.Interop/Java.Interop/JniType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,6 @@ namespace Java.Interop {

public sealed class JniType : IDisposable {

readonly static KeyValuePair<string, string>[] BuiltinMappings = new KeyValuePair<string, string>[] {
new KeyValuePair<string, string>("byte", "B"),
new KeyValuePair<string, string>("boolean", "Z"),
new KeyValuePair<string, string>("char", "C"),
new KeyValuePair<string, string>("double", "D"),
new KeyValuePair<string, string>("float", "F"),
new KeyValuePair<string, string>("int", "I"),
new KeyValuePair<string, string>("long", "J"),
new KeyValuePair<string, string>("short", "S"),
new KeyValuePair<string, string>("void", "V"),
};

public static unsafe JniType DefineClass (string name, JniReferenceSafeHandle loader, byte[] classFileData)
{
fixed (byte* buf = classFileData) {
Expand Down Expand Up @@ -57,14 +45,7 @@ public string Name {
get {
AssertValid ();

var s = JniEnvironment.Current.Class_getName.CallVirtualObjectMethod (SafeHandle);
var n = JniEnvironment.Strings.ToString (s, JniHandleOwnership.Transfer)
.Replace ('.', '/');
for (int i = 0; i < BuiltinMappings.Length; ++i) {
if (n == BuiltinMappings [i].Key)
return BuiltinMappings [i].Value;
}
return n;
return JniEnvironment.Types.GetJniTypeNameFromClass (SafeHandle);
}
}

Expand Down
Loading

0 comments on commit 46b7aee

Please sign in to comment.