From 999d5945c10a9c0296d43a73a0041ed5cb4257a8 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 4 Jun 2020 18:20:38 +0200 Subject: [PATCH 1/3] Optimize method accessor plugin --- .../Data/Core/Plugins/MethodAccessorPlugin.cs | 93 ++++++++++++++----- ...sorBenchmarks.cs => AccessorBenchmarks.cs} | 37 +++++++- 2 files changed, 105 insertions(+), 25 deletions(-) rename tests/Avalonia.Benchmarks/Data/{PropertyAccessorBenchmarks.cs => AccessorBenchmarks.cs} (58%) diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index c19ee8dba7..4aa6434de2 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -1,13 +1,16 @@ using System; -using System.Linq; +using System.Collections.Generic; +using System.Linq.Expressions; using System.Reflection; namespace Avalonia.Data.Core.Plugins { - class MethodAccessorPlugin : IPropertyAccessorPlugin + public class MethodAccessorPlugin : IPropertyAccessorPlugin { - public bool Match(object obj, string methodName) - => obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName); + private readonly Dictionary<(Type, string), MethodInfo> _methodLookup = + new Dictionary<(Type, string), MethodInfo>(); + + public bool Match(object obj, string methodName) => GetFirstMethodWithName(obj.GetType(), methodName) != null; public IPropertyAccessor Start(WeakReference reference, string methodName) { @@ -15,17 +18,22 @@ namespace Avalonia.Data.Core.Plugins Contract.Requires(methodName != null); reference.TryGetTarget(out object instance); - var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName); + + var method = GetFirstMethodWithName(instance.GetType(), methodName); if (method != null) { - if (method.GetParameters().Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) + var parameters = method.GetParameters(); + + if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) { - var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(methodName)); + var exception = new ArgumentException( + "Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", + nameof(methodName)); return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); } - return new Accessor(reference, method); + return new Accessor(reference, method, parameters); } else { @@ -35,31 +43,72 @@ namespace Avalonia.Data.Core.Plugins } } + private MethodInfo GetFirstMethodWithName(Type type, string methodName) + { + var key = (type, methodName); + + if (!_methodLookup.TryGetValue(key, out MethodInfo methodInfo)) + { + methodInfo = TryFindAndCacheMethod(type, methodName); + } + + return methodInfo; + } + + private MethodInfo TryFindAndCacheMethod(Type type, string methodName) + { + MethodInfo found = null; + + const BindingFlags bindingFlags = + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + var methods = type.GetMethods(bindingFlags); + + foreach (MethodInfo methodInfo in methods) + { + if (methodInfo.Name == methodName) + { + found = methodInfo; + + break; + } + } + + _methodLookup.Add((type, methodName), found); + + return found; + } + private sealed class Accessor : PropertyAccessorBase { - public Accessor(WeakReference reference, MethodInfo method) + public Accessor(WeakReference reference, MethodInfo method, ParameterInfo[] parameters) { Contract.Requires(reference != null); Contract.Requires(method != null); - var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); var returnType = method.ReturnType; - - if (returnType == typeof(void)) + bool hasReturn = returnType != typeof(void); + + var signatureTypeCount = hasReturn ? 1 : 0 + parameters.Length; + + var paramTypes = new Type[signatureTypeCount]; + + for (var i = 0; i < parameters.Length; i++) { - if (paramTypes.Length == 0) - { - PropertyType = typeof(Action); - } - else - { - PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes); - } + ParameterInfo parameter = parameters[i]; + + paramTypes[i] = parameter.ParameterType; + } + + if (hasReturn) + { + paramTypes[paramTypes.Length - 1] = returnType; + + PropertyType = Expression.GetFuncType(paramTypes); } else { - var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); - PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); + PropertyType = Expression.GetActionType(paramTypes); } if (method.IsStatic) diff --git a/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs b/tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs similarity index 58% rename from tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs rename to tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs index 5c2502422a..3d5a4029bb 100644 --- a/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs @@ -10,7 +10,8 @@ namespace Avalonia.Benchmarks.Data [MemoryDiagnoser, InProcess] public class PropertyAccessorBenchmarks { - private readonly InpcPropertyAccessorPlugin _plugin = new InpcPropertyAccessorPlugin(); + private readonly InpcPropertyAccessorPlugin _inpcPlugin = new InpcPropertyAccessorPlugin(); + private readonly MethodAccessorPlugin _methodPlugin = new MethodAccessorPlugin(); private readonly TestObject _targetStrongRef = new TestObject(); private readonly WeakReference _targetWeakRef; @@ -20,9 +21,27 @@ namespace Avalonia.Benchmarks.Data } [Benchmark] - public void InpcAccessor() + public void InpcAccessorMatch() { - _plugin.Start(_targetWeakRef, nameof(TestObject.Test)); + _inpcPlugin.Match(_targetWeakRef, nameof(TestObject.Test)); + } + + [Benchmark] + public void InpcAccessorStart() + { + _inpcPlugin.Start(_targetWeakRef, nameof(TestObject.Test)); + } + + [Benchmark] + public void MethodAccessorMatch() + { + _methodPlugin.Match(_targetWeakRef, nameof(TestObject.Execute)); + } + + [Benchmark] + public void MethodAccessorStart() + { + _methodPlugin.Start(_targetWeakRef, nameof(TestObject.Execute)); } private class TestObject : INotifyPropertyChanged @@ -45,6 +64,18 @@ namespace Avalonia.Benchmarks.Data } } + public void Execute() + { + } + + public void Execute(object p0) + { + } + + public void Execute(object p0, object p1) + { + } + public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] From 0f23da0a79a90be415de2e4959f62e509b5aaf86 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 4 Jun 2020 18:34:05 +0200 Subject: [PATCH 2/3] Use correct file name. --- .../Data/{AccessorBenchmarks.cs => PropertyAccessorBenchmarks.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/Avalonia.Benchmarks/Data/{AccessorBenchmarks.cs => PropertyAccessorBenchmarks.cs} (100%) diff --git a/tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs b/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs similarity index 100% rename from tests/Avalonia.Benchmarks/Data/AccessorBenchmarks.cs rename to tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs From 4b6d601bb70f95635d02edb785a62c8037b938b6 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 4 Jun 2020 19:11:38 +0200 Subject: [PATCH 3/3] Fix ternary op. --- src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 4aa6434de2..5d694f4cf9 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -89,7 +89,7 @@ namespace Avalonia.Data.Core.Plugins var returnType = method.ReturnType; bool hasReturn = returnType != typeof(void); - var signatureTypeCount = hasReturn ? 1 : 0 + parameters.Length; + var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length; var paramTypes = new Type[signatureTypeCount];