pamidur / aspect-injector

AOP framework for .NET (c#, vb, etc)
Apache License 2.0
745 stars 112 forks source link

Cache delagates and metadata for performance improvements #137

Open arontsang opened 3 years ago

arontsang commented 3 years ago

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

pamidur commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

pamidur commented 2 years ago

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

jerviscui commented 2 years ago

There are improvements. So cool!