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

Cache delagates and metadata for performance improvements #137

Open
arontsang opened this issue Sep 23, 2020 · 7 comments
Open

Cache delagates and metadata for performance improvements #137

arontsang opened this issue Sep 23, 2020 · 7 comments

Comments

@arontsang
Copy link

A reflection call on each invocation limits performance when using the metadata argument.

@pamidur
Copy link
Owner

pamidur commented Sep 24, 2020

Hi @arontsang , you are right. Actual benchmarks show that performance difference is big enough.

|     Method |           Job |       Runtime |      Mean |     Error |    StdDev | Ratio | RatioSD |
|----------- |-------------- |-------------- |----------:|----------:|----------:|------:|--------:|
| Reflection |    .NET 4.7.2 |    .NET 4.7.2 | 1.1873 ns | 0.0425 ns | 0.0398 ns |  1.00 |    0.00 |
| Reflection | .NET Core 3.1 | .NET Core 3.1 | 1.1554 ns | 0.0447 ns | 0.0418 ns |  0.97 |    0.05 |
|            |               |               |           |           |           |       |         |
|      Cache |    .NET 4.7.2 |    .NET 4.7.2 | 0.1364 ns | 0.0182 ns | 0.0162 ns |  1.00 |    0.00 |
|      Cache | .NET Core 3.1 | .NET Core 3.1 | 0.1495 ns | 0.0200 ns | 0.0178 ns |  1.11 |    0.20 |

Please feel free to contribute!

@arontsang
Copy link
Author

I tried to contribute this feature, but unfortunately, it is a little too complicated.
I found that my FieldAccessors for classes nested in a generic class failed

public Foo<T>
{
     public class Bar
     {
           public void Method() => {}
           // Could not access the MethodInfo Cache Field.
     }
}

@pamidur
Copy link
Owner

pamidur commented Oct 9, 2020

Sorry for delay, was on vacation)

Yes - generics are tricky, half of all bugs in this project are generics related.

For example you can take a look at here. In short, you need not to call FieldDef itself, but rather FieldRef with DeclaringType=generic TypeRef of class where field resides.

But if it doesn't help I will definitely add this feature in future.

@arontsang
Copy link
Author

Here is my attempt at this.

As I said before, it is failing on generics.

At present, things that aren't done:

  1. Name mangling, for overloads (each overload sig needs a unique Field)
  2. Short circuit code for generic functions (as opposed to generic classes), since it would be crazy to cache all variations of T.
  3. Add tests for classes that both have and lack static constructors.

AdviceWeaveProcessBase<TEffect>


        protected virtual Cut LoadMethodArgument(Cut pc, AdviceArgument parameter)
        {
            var methodInfoName = _method.Name + "_MethodInfo";
            FieldDefinition methodCache = _type.Fields.FirstOrDefault(x => x.Name == methodInfoName);
            if(methodCache == null)
            {
                methodCache = new FieldDefinition(
                    methodInfoName,
                    FieldAttributes.Public | FieldAttributes.Static,
                    _type.Module.ImportReference(WellKnownTypes.MethodBase));

                _type.Fields.Add(methodCache);

                MethodDefinition GetTypeStaticConstructor()
                {
                    var staticConstructor = _type.GetStaticConstructor();
                    if (staticConstructor != null) return staticConstructor;

                    var methodAttributes = MethodAttributes.Static | MethodAttributes.HideBySig |
                                           MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
                    staticConstructor = new MethodDefinition(".cctor", methodAttributes,
                        _type.Module.ImportReference(StandardTypes.Void));
                    staticConstructor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
                    staticConstructor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
                    _type.Methods.Add(staticConstructor);
                    return staticConstructor;
                }

                GetTypeStaticConstructor()
                    .Body
                    .BeforeExit(cut =>
                    {
                        return cut.Here(x => x.MethodOf(_method).Cast(StandardTypes.Object, WellKnownTypes.MethodBase))
                            .Write(OpCodes.Stsfld, new FieldReference(methodInfoName, _type));
                    });

            }
            return pc
                .Write(OpCodes.Ldsfld, new FieldReference(methodInfoName, _type));
        }

@pamidur
Copy link
Owner

pamidur commented Oct 15, 2020

Hi @arontsang
Well done! Could you make it a PR ?
Regarding missing things:

  • Naming can be done the same way it is done for around method -> Take metadata token as method name
  • For generic methods - idk - store generic<> cache and make generic on call? Yes - slower, but easier

@pamidur
Copy link
Owner

pamidur commented Oct 18, 2021

If Aspect around the method, it will generate code like:

private object __a$_around_Method_100663472_w_0(object[] A_1)
{
    return this.Aspect.Around(new Func<object[], object>(this.__a$_around_Method_100663472_u), A_1);
}

The new Func<>() method will be exectued each time for create Delegate. How about use a static field cache Delegate,

static Func<@classType, object[], object> Func = this.__a$_around_Method_100663472_u;

private object __a$_around_Method_100663472_w_0(object[] A_1)
{
    return this.Aspect.Around(Func, this, A_1);
}

private static object __a$_around_Method_100663472_u(@classType instance, object[] A_1)
{
    return instance.__a$_around_Method_100663472_o((int)A_1[0]);
}

Do you think it's worth it?
@jerviscui

@pamidur pamidur changed the title Metadata argument should be cached for performance Cached delagates and metadata for performance improvements Oct 18, 2021
@pamidur pamidur changed the title Cached delagates and metadata for performance improvements Cache delagates and metadata for performance improvements Oct 18, 2021
@jerviscui
Copy link
Contributor

There are improvements. So cool!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants