From cd4bdcd5f06ab9d05359426e4e739520bc85b843 Mon Sep 17 00:00:00 2001 From: dd Date: Mon, 18 Oct 2021 18:15:25 +0200 Subject: [PATCH] implemented MethodInfoCompileTimeWeaver to speed up MethodBase.GetCurrentMethod() performance #85 --- .../PeVerifier.cs | 2 + .../AsyncMethodWeaver.cs | 30 ++-- .../InstructionBlockChainCreator.cs | 24 ++- .../MethodInfoCompileTimeWeaver.cs | 148 ++++++++++++++++++ src/MethodBoundaryAspect.Fody/MethodWeaver.cs | 12 +- .../MethodWeaverFactory.cs | 16 +- src/MethodBoundaryAspect.Fody/ModuleWeaver.cs | 23 ++- 7 files changed, 229 insertions(+), 26 deletions(-) create mode 100644 src/MethodBoundaryAspect.Fody/MethodInfoCompileTimeWeaver.cs diff --git a/src/MethodBoundaryAspect.Fody.UnitTests.Shared/PeVerifier.cs b/src/MethodBoundaryAspect.Fody.UnitTests.Shared/PeVerifier.cs index a4265f6..6e6590b 100644 --- a/src/MethodBoundaryAspect.Fody.UnitTests.Shared/PeVerifier.cs +++ b/src/MethodBoundaryAspect.Fody.UnitTests.Shared/PeVerifier.cs @@ -42,10 +42,12 @@ private static string GetPeVerifyPath() var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); var possiblePeVerifyPaths = new[] { + $@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\peverify.exe", $@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.2 Tools\peverify.exe", $@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.1 Tools\peverify.exe", $@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7 Tools\peverify.exe", $@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\peverify.exe", + $@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\peverify.exe", $@"{folderPath}\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\peverify.exe" }; diff --git a/src/MethodBoundaryAspect.Fody/AsyncMethodWeaver.cs b/src/MethodBoundaryAspect.Fody/AsyncMethodWeaver.cs index 2ea8e84..df7fb70 100644 --- a/src/MethodBoundaryAspect.Fody/AsyncMethodWeaver.cs +++ b/src/MethodBoundaryAspect.Fody/AsyncMethodWeaver.cs @@ -10,16 +10,26 @@ namespace MethodBoundaryAspect.Fody { public class AsyncMethodWeaver : MethodWeaver { - MethodDefinition _moveNext; - Instruction _setupPointer; - FieldReference _executionArgsField; - VariableDefinition _stateMachineLocal; - TypeReference _stateMachine; - - public AsyncMethodWeaver(ModuleDefinition module, MethodDefinition method, MethodDefinition moveNext, IList aspects) : - base(module, method, aspects) + private readonly MethodDefinition _moveNext; + private readonly MethodInfoCompileTimeWeaver _methodInfoCompileTimeWeaver; + + private readonly VariableDefinition _stateMachineLocal; + private readonly TypeReference _stateMachine; + + private Instruction _setupPointer; + private FieldReference _executionArgsField; + + public AsyncMethodWeaver( + ModuleDefinition module, + MethodDefinition method, + MethodDefinition moveNext, + IList aspects, + MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver) : + base(module, method, aspects, methodInfoCompileTimeWeaver) { _moveNext = moveNext; + _methodInfoCompileTimeWeaver = methodInfoCompileTimeWeaver; + var instructions = _ilProcessor.Body.Instructions; if (instructions.Count < 2) throw new InvalidOperationException($"Async state machine on {method.FullName} not in expected configuration."); @@ -99,7 +109,9 @@ protected override void WeaveMethodExecutionArgs(NamedInstructionBlockChain argu { var executionArgs = _creator.CreateMethodExecutionArgsInstance( arguments, - _aspects[0].Info.AspectAttribute.AttributeType); + _aspects[0].Info.AspectAttribute.AttributeType, + _method, + _methodInfoCompileTimeWeaver); _executionArgsField = _module.ImportReference(_stateMachine.AddPublicInstanceField(executionArgs.Variable.VariableType)); executionArgs.Add(new InstructionBlock("", Instruction.Create(OpCodes.Ldloc, executionArgs.Variable))); diff --git a/src/MethodBoundaryAspect.Fody/InstructionBlockChainCreator.cs b/src/MethodBoundaryAspect.Fody/InstructionBlockChainCreator.cs index fb8713e..31e4fa3 100644 --- a/src/MethodBoundaryAspect.Fody/InstructionBlockChainCreator.cs +++ b/src/MethodBoundaryAspect.Fody/InstructionBlockChainCreator.cs @@ -105,7 +105,9 @@ public NamedInstructionBlockChain CreateThisVariable(TypeReference typeReference public NamedInstructionBlockChain CreateMethodExecutionArgsInstance( NamedInstructionBlockChain argumentsArrayChain, - TypeReference anyAspectTypeDefinition) + TypeReference anyAspectTypeDefinition, + MethodDefinition method, + MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver) { // instance value var createThisVariableBlock = CreateThisVariable(); @@ -140,15 +142,27 @@ public NamedInstructionBlockChain CreateMethodExecutionArgsInstance( var methodBaseTypeRef = _referenceFinder.GetTypeReference(typeof (MethodBase)); var methodBaseVariable = _creator.CreateVariable(methodBaseTypeRef); - var methodBaseGetCurrentMethod = _referenceFinder.GetMethodReference(methodBaseTypeRef, - md => md.Name == "GetCurrentMethod"); - var callGetCurrentMethodBlock = _creator.CallStaticMethod(methodBaseGetCurrentMethod, new VariablePersistable(methodBaseVariable)); + InstructionBlock callGetCurrentMethodBlock; + var variablePersistable = new VariablePersistable(methodBaseVariable); + if (methodInfoCompileTimeWeaver?.IsEnabled != true) + { + // fallback: slow GetCurrentMethod + var methodBaseGetCurrentMethod = _referenceFinder.GetMethodReference(methodBaseTypeRef, + md => md.Name == "GetCurrentMethod"); + callGetCurrentMethodBlock = _creator.CallStaticMethod(methodBaseGetCurrentMethod, variablePersistable); + } + else + { + // fast: precompiled method info token + methodInfoCompileTimeWeaver.AddMethod(method); + callGetCurrentMethodBlock = methodInfoCompileTimeWeaver.PushMethodInfoOnStack(method, variablePersistable); + } var methodExecutionArgsSetMethodBaseMethodRef = _referenceFinder.GetMethodReference(methodExecutionArgsTypeRef, md => md.Name == "set_Method"); var callSetMethodBlock = _creator.CallVoidInstanceMethod(methodExecutionArgsSetMethodBaseMethodRef, new VariablePersistable(methodExecutionArgsVariable), - new VariablePersistable(methodBaseVariable)); + variablePersistable); var newMethodExecutionArgsBlockChain = new NamedInstructionBlockChain(methodExecutionArgsVariable, methodExecutionArgsTypeRef); diff --git a/src/MethodBoundaryAspect.Fody/MethodInfoCompileTimeWeaver.cs b/src/MethodBoundaryAspect.Fody/MethodInfoCompileTimeWeaver.cs new file mode 100644 index 0000000..c3e5ea6 --- /dev/null +++ b/src/MethodBoundaryAspect.Fody/MethodInfoCompileTimeWeaver.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using FieldAttributes = Mono.Cecil.FieldAttributes; +using MethodAttributes = Mono.Cecil.MethodAttributes; +using TypeAttributes = Mono.Cecil.TypeAttributes; + +namespace MethodBoundaryAspect.Fody +{ + public class MethodInfoCompileTimeWeaver + { + private readonly ModuleDefinition _mainModule; + private TypeDefinition _methodInfosClass; + + private readonly Dictionary _fieldsCache + = new Dictionary(); + + private readonly string _cacheNamespace = "OnMethodBoundaryAspectCompile"; + private readonly string _cacheClass = "MethodInfos"; + + public MethodInfoCompileTimeWeaver(ModuleDefinition module) + { + _mainModule = module; + } + + public bool IsEnabled { get; set; } + + public void EnsureInit() + { + if (_methodInfosClass != null) + return; + + var classAttributes = TypeAttributes.Class + | TypeAttributes.Public + | TypeAttributes.Abstract // abstract + sealed => static + | TypeAttributes.Sealed; + _methodInfosClass = new TypeDefinition(_cacheNamespace, _cacheClass, classAttributes) + { + BaseType = GetTypeReference(typeof(object)) + }; + _mainModule.Types.Add(_methodInfosClass); + } + + public void AddMethod(MethodDefinition method) + { + EnsureInit(); + + var typeReferenceMethodBase = GetTypeReference(typeof(MethodBase)); + _mainModule.ImportReference(typeReferenceMethodBase); + + var identifier = CreateIdentifier(method); + var fieldAttributes = FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly; + var fd = new FieldDefinition($"_methodInfo_{identifier}", fieldAttributes, typeReferenceMethodBase); + _methodInfosClass.Fields.Add(fd); + + _fieldsCache.Add(method, fd); + } + + public InstructionBlock PushMethodInfoOnStack(MethodDefinition method, VariablePersistable variablePersistable) + { + var fieldDefinition = _fieldsCache[method]; + var instruction = Instruction.Create(OpCodes.Ldsfld, fieldDefinition); + var store = variablePersistable.Store( + new InstructionBlock("", instruction), + variablePersistable.PersistedType); + return new InstructionBlock($"Load method info for '{method.Name}'", store.Instructions); + } + + public void Finish() + { + if (_fieldsCache.Any()) + CreateStaticCtor(); + } + + private string CreateIdentifier(MemberReference method) + { + var bytes = Encoding.UTF8.GetBytes(method.FullName); + var sha256 = new SHA256Managed(); + var hash = sha256.ComputeHash(bytes); + return BitConverter.ToString(hash).Replace("-", string.Empty); + } + + private void CreateStaticCtor() + { + var typeReferenceVoid = GetTypeReference(typeof(void)); + var staticConstructorAttributes = + MethodAttributes.Private | + MethodAttributes.HideBySig | + MethodAttributes.SpecialName | + MethodAttributes.RTSpecialName | + MethodAttributes.Static; + var cctor = new MethodDefinition(".cctor", staticConstructorAttributes, typeReferenceVoid); + + // taken from https://gist.github.com/jbevain/390902 + var getMethodFromHandle = ImportGetMethodFromHandle(); + var cctorInstructions = new List(); + foreach (var entry in _fieldsCache) + { + cctorInstructions.Add(Instruction.Create(OpCodes.Ldtoken, entry.Key)); + cctorInstructions.Add(Instruction.Create(OpCodes.Call, getMethodFromHandle)); + cctorInstructions.Add(Instruction.Create(OpCodes.Stsfld, entry.Value)); + } + + cctorInstructions.Add(Instruction.Create(OpCodes.Ret)); + + foreach (var methodInstruction in cctorInstructions) + cctor.Body.Instructions.Add(methodInstruction); + cctor.Body.Optimize(); + + _methodInfosClass.Methods.Add(cctor); + } + + private TypeReference GetTypeReference(Type type) + { + return GetTypeReference(type.FullName); + } + + private TypeReference GetTypeReference(string name) + { + switch (name) + { + case "System.Object": + return _mainModule.TypeSystem.Object; + case "System.Void": + return _mainModule.TypeSystem.Void; + default: + { + var splitted = name.Split('.'); + var namespaceName = string.Join(".", splitted.Take(splitted.Length - 1)); + var typeName = splitted.Last(); + return new TypeReference(namespaceName, typeName, _mainModule, _mainModule.TypeSystem.CoreLibrary); + } + } + } + + private MethodReference ImportGetMethodFromHandle() + { + return _mainModule.ImportReference(typeof(MethodBase) + .GetMethod("GetMethodFromHandle", new[] {typeof(RuntimeMethodHandle)})); + } + } +} diff --git a/src/MethodBoundaryAspect.Fody/MethodWeaver.cs b/src/MethodBoundaryAspect.Fody/MethodWeaver.cs index d4be734..206d34c 100644 --- a/src/MethodBoundaryAspect.Fody/MethodWeaver.cs +++ b/src/MethodBoundaryAspect.Fody/MethodWeaver.cs @@ -16,19 +16,25 @@ public class MethodWeaver protected readonly InstructionBlockChainCreator _creator; protected readonly ILProcessor _ilProcessor; protected readonly IList _aspects; + private readonly MethodInfoCompileTimeWeaver _methodInfoCompileTimeWeaver; protected bool HasMultipleAspects => _aspects.Count > 1; protected IPersistable ExecutionArgs { get; set; } public int WeaveCounter { get; private set; } - public MethodWeaver(ModuleDefinition module, MethodDefinition method, IList aspects) + public MethodWeaver( + ModuleDefinition module, + MethodDefinition method, + IList aspects, + MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver) { _module = module; _method = method; _creator = new InstructionBlockChainCreator(method, module); _ilProcessor = _method.Body.GetILProcessor(); _aspects = aspects; + _methodInfoCompileTimeWeaver = methodInfoCompileTimeWeaver; } public void Weave() @@ -192,7 +198,9 @@ protected virtual void WeaveMethodExecutionArgs(NamedInstructionBlockChain argum { var executionArgs = _creator.CreateMethodExecutionArgsInstance( arguments, - _aspects[0].Info.AspectAttribute.AttributeType); + _aspects[0].Info.AspectAttribute.AttributeType, + _method, + _methodInfoCompileTimeWeaver); AddToSetup(executionArgs); ExecutionArgs = executionArgs; } diff --git a/src/MethodBoundaryAspect.Fody/MethodWeaverFactory.cs b/src/MethodBoundaryAspect.Fody/MethodWeaverFactory.cs index 63195d0..2b560a7 100644 --- a/src/MethodBoundaryAspect.Fody/MethodWeaverFactory.cs +++ b/src/MethodBoundaryAspect.Fody/MethodWeaverFactory.cs @@ -8,7 +8,10 @@ namespace MethodBoundaryAspect.Fody { public static class MethodWeaverFactory { - public static MethodWeaver MakeWeaver(ModuleDefinition module, MethodDefinition method, IEnumerable aspects) + public static MethodWeaver MakeWeaver(ModuleDefinition module, + MethodDefinition method, + IEnumerable aspects, + MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver) { var filteredAspects = from a in aspects let methods = GetUsedAspectMethods(a.AspectAttribute.AttributeType) @@ -19,12 +22,17 @@ public static MethodWeaver MakeWeaver(ModuleDefinition module, MethodDefinition if (asyncAttribute == null) { var aspectList = filteredAspects.Select(a => new AspectData(a.Aspect, a.Methods, method, module)).ToList(); - return new MethodWeaver(module, method, aspectList); + return new MethodWeaver(module, method, aspectList, methodInfoCompileTimeWeaver); } var moveNextMethod = ((TypeDefinition)asyncAttribute.ConstructorArguments[0].Value).Methods.First(m => m.Name == "MoveNext"); - return new AsyncMethodWeaver(module, method, moveNextMethod, - filteredAspects.Select(a => new AspectDataOnAsyncMethod(moveNextMethod, a.Aspect, a.Methods, method, module)).ToList()); + var aspectDatas = filteredAspects.Select(a => new AspectDataOnAsyncMethod(moveNextMethod, a.Aspect, a.Methods, method, module)).ToList(); + return new AsyncMethodWeaver( + module, + method, + moveNextMethod, + aspectDatas, + methodInfoCompileTimeWeaver); } static AspectMethods GetUsedAspectMethods(TypeReference aspectTypeDefinition) diff --git a/src/MethodBoundaryAspect.Fody/ModuleWeaver.cs b/src/MethodBoundaryAspect.Fody/ModuleWeaver.cs index f8665cf..25dd6d4 100644 --- a/src/MethodBoundaryAspect.Fody/ModuleWeaver.cs +++ b/src/MethodBoundaryAspect.Fody/ModuleWeaver.cs @@ -33,10 +33,14 @@ namespace MethodBoundaryAspect.Fody /// public class ModuleWeaver : BaseModuleWeaver { + private MethodInfoCompileTimeWeaver _methodInfoCompileTimeWeaver; + public ModuleWeaver() { InitLogging(); } + + public bool DisableCompileTimeMethodInfos { get; set; } public int TotalWeavedTypes { get; private set; } public int TotalWeavedMethods { get; private set; } @@ -48,7 +52,12 @@ public ModuleWeaver() public override void Execute() { + _methodInfoCompileTimeWeaver = new MethodInfoCompileTimeWeaver(ModuleDefinition); + _methodInfoCompileTimeWeaver.IsEnabled = !DisableCompileTimeMethodInfos; + Execute(ModuleDefinition); + + _methodInfoCompileTimeWeaver.Finish(); } public override IEnumerable GetAssembliesForScanning() @@ -142,7 +151,7 @@ private void Execute(ModuleDefinition module) { var assemblyMethodBoundaryAspects = module.Assembly.CustomAttributes; - foreach (var type in module.Types) + foreach (var type in module.Types.ToList()) WeaveTypeAndNestedTypes(module, type, assemblyMethodBoundaryAspects); } @@ -207,14 +216,15 @@ private void WeaveType( .ToList(); if (aspectInfos.Count == 0) continue; - + foreach (var aspectInfo in aspectInfos) aspectInfo.InitOrderIndex(assemblyMethodBoundaryAspects, classMethodBoundaryAspects, methodMethodBoundaryAspects); weavedAtLeastOneMethod = WeaveMethod( module, method, - aspectInfos); + aspectInfos, + _methodInfoCompileTimeWeaver); } if (weavedAtLeastOneMethod) @@ -222,16 +232,17 @@ private void WeaveType( } private bool WeaveMethod( - ModuleDefinition module, + ModuleDefinition module, MethodDefinition method, - List aspectInfos) + List aspectInfos, + MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver) { aspectInfos = AspectOrderer.Order(aspectInfos); var aspectInfosWithMethods = aspectInfos .Where(x => !x.SkipProperties || (!method.IsGetter && !method.IsSetter)) .ToList(); - var methodWeaver = MethodWeaverFactory.MakeWeaver(module, method, aspectInfosWithMethods); + var methodWeaver = MethodWeaverFactory.MakeWeaver(module, method, aspectInfosWithMethods, methodInfoCompileTimeWeaver); methodWeaver.Weave(); if (methodWeaver.WeaveCounter == 0) return false;