Skip to content
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

Refactor dynamic method names creation #2025

Merged

Conversation

radekdoulik
Copy link
Member

@radekdoulik radekdoulik commented Jul 31, 2018

Put the dynamic method name logic in one place. It also removes the
formatted string use and uses InvariantCulture.

It hopefully improves the performance (simpler string operations,
InvariantCulture). The change in performance is so small though, that
I wasn't able to measure it.

We use long counter, because ConstructorBuilder can, in some
cases, create a new constructor per java object instance. This should
be addressed in the future. After that we can probably get back to
int counter.

Also associate the created dynamic methods with
DynamicMethodNameCounter class instead of System.Object

internal class DynamicMethodNameCounter {
static int dynamicMethodNameCounter;

internal static string UniqueName
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't match our normal formatting guidelines; { should be on the same line as the property name.

However, this should be a property; it should be a method. "Normal" property semantics are that the getter won't return a different value every time it's called. (Yes, DateTime.Now doesn't do that. Yes, it should never have been a property in the first place. We shouldn't repeat the design mistakes of DateTime.Now.)

Since this will return a new value every time it's called, it should be a method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is one of the conventions, which doesn't make much sense to me :-)


internal static Action <IntPtr, object []> CreateDelegate (Type type, ConstructorInfo cinfo, Type [] parameter_types) {
var handle = handlefld;
if (typeof (Java.Lang.Throwable).IsAssignableFrom (type)) {
handle = Throwable_handle;
}

DynamicMethod method = new DynamicMethod ($"{Interlocked.Increment (ref dynamicMethodNameCounter)}c", typeof (void), new Type [] {typeof (IntPtr), typeof (object []) }, typeof (object), true);
DynamicMethod method = new DynamicMethod (DynamicMethodNameCounter.UniqueName, typeof (void), new Type [] {typeof (IntPtr), typeof (object []) }, typeof (DynamicMethodNameCounter), true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want constructors and methods to share the same counter?

I'm not even sure what range is "normal" for this counter, other than that the counter will increment for every constructor invocation and method registered.

Also note that -- at present -- the ConstructorBuilder count increments every time TypeManager.n_Activate() is invoked. It's not incremented once per method signature; it's incremented once per invocation. This is entirely unbound, and it's entirely imaginable to have Java code construct ~4 billion objects over the course of a (very) long-lived process, which would cause an overflow to occur.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...that said, we still have that possible overflow problem now, because of PR #1872...and I didn't think of this scenario before. Doh! :-(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that really true? My understanding is that it is called once per type at most (many types don't have <init> constructors) and not per instance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that really true?

Let's start with Java Callable Wrappers:

    public HelloAndroid ()
    {
        super ();
        if (getClass () == HelloAndroid.class)
            mono.android.TypeManager.Activate (
                "Mono.Samples.HelloWorld.HelloAndroid, HelloWorld, Version=1.0.0.0, 
                Culture=neutral, PublicKeyToken=null", "", this, new java.lang.Object[] {  });
    }

TypeManager.Activate() is TypeManager.n_Activate(), and is invoked separately for every constructor invocation:

// ~~~ From Java ~~~

new HelloAndroid();
// One TypeManager.n_Activate() invocation

new HelloAndroid();
// Two TypeManager.n_Activate() invocations

new HelloAndroid();
// Three TypeManager.n_Activate() invocations

...

Which brings us to the ConstructorBuilder.CreateDelegate() invocation, for which nothing is cached. (We should absolutely add some caching; it would have noticeable improvements in instance construction! However, we don't currently have any such caching.)

Which is why I said:

the ConstructorBuilder count increments every time TypeManager.n_Activate() is invoked.

I don't see any other interpretation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I will need to take look at it. Hopefully we can address that in #2022.

Meanwhile I have updated the patch so, that we get back to using Guid once we create int.MaxValue methods. I am curious whether it would even be possible to associate that many methods with a type :-) Didn't have time to try it yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious whether it would even be possible to associate that many methods with a type :-)

It isn't possible to associate int.MaxValue methods with a type; the Java .class file format only permits ushort.MaxValue values within the "constant pool" of a .class file, and each method declared within a Java type must have one or more entries in the constant pool.

However, dynamicMethodNameCounter isn't "scoped" to a single type; it's used across all types.

If it were limited to just methods and not constructors, this means the developer would need to register int.MaxValue/ushort.MaxValue=32768 types in order to overflow the counter.

This is unlikely. :-)

The problem, again, is constructors: constructors are not per-type, they're per-invocation. If Java had an equivalent to ConstructorInfo.Invoke(object, object[]) -- which perhaps might not exist in Java, but let's go with it -- you would only need to initialize one type, and one instance, and just call that one method int.MaxValue times.

That's excessive, but seems considerably more likely than having 32768 types in a .apk.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the type on the managed side here, where the methods/constructors are associated to DynamicMethodCounter class (or System.Object as before).

@radekdoulik radekdoulik force-pushed the pr-refactor-dynamic-method-name-creation branch 2 times, most recently from c37130a to d631005 Compare August 2, 2018 19:53
if (dynamicMethodNameCounter < int.MaxValue)
return Interlocked.Increment (ref dynamicMethodNameCounter).ToString (System.Globalization.CultureInfo.InvariantCulture);

return Guid.NewGuid ().ToString ();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not thrilled with any use of Guid.

Would it be possible to separate constructor from method counting? We "know" -- or are reasonably sure -- that it's highly unlikely that methods will cause an overflow. Constructors can...maybe. At least, I find it harder to convince myself that Constructors cannot cause an overflow.

I'm inclined to think we should split out method counts from constructor counts.

...and perhaps Constructors should use a long? Guid is really, really, slow. Interlocked.Increment(ref long) would still be faster, by many orders of magnitude.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just have long here and keep one counter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a long would probably be fine, but I suspect -- entirely untested! -- that Interlocked.Increment() on a long will be slower than an int on 32-bit hardware.

Then again, how much longer will 32-bit Android devices be a priority?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, updated to use long counter. Hopefully we can fix ConstructorBuilder soon and get back to int.

Put the dynamic method name logic in one place. It also removes the
formatted string use and uses `InvariantCulture`.

It hopefully improves the performance (simpler string operations,
InvariantCulture). The change in performance is so small though, that
I wasn't able to measure it.

We use `long` counter, because `ConstructorBuilder` can, in some
cases, create a new constructor per java object instance. This should
be addressed in the future. After that we can probably get back to
`int` counter.
@radekdoulik radekdoulik force-pushed the pr-refactor-dynamic-method-name-creation branch from d631005 to 0e86470 Compare August 7, 2018 12:36
@jonpryor jonpryor merged commit 0ec0106 into dotnet:master Aug 7, 2018
@github-actions github-actions bot locked and limited conversation to collaborators Feb 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants