-
-
Notifications
You must be signed in to change notification settings - Fork 396
Additional Performance Improvements #359
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
base: master
Are you sure you want to change the base?
Changes from all commits
3eb8e42
42fbaec
59c6cb9
4576ce6
cb7ea5f
1b000f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,38 +9,119 @@ namespace DynamicExpresso.Reflection | |
{ | ||
internal class MemberFinder | ||
{ | ||
private struct MemberTypeKey | ||
{ | ||
public Type _type; | ||
public string _memberName; | ||
public BindingFlags _flags; | ||
|
||
public MemberTypeKey(Type type, string memberName, BindingFlags flags) | ||
{ | ||
_type = type; | ||
_memberName = memberName; | ||
_flags = flags; | ||
} | ||
} | ||
|
||
private class MemberTypeKeyEqualityComparer : IEqualityComparer<MemberTypeKey> | ||
{ | ||
public static readonly MemberTypeKeyEqualityComparer Instance = new MemberTypeKeyEqualityComparer(); | ||
|
||
public bool Equals(MemberTypeKey x, MemberTypeKey y) | ||
{ | ||
return x._type.Equals(y._type) && x._flags == y._flags && x._memberName.Equals(y._memberName, StringComparison.Ordinal); | ||
} | ||
|
||
|
||
public int GetHashCode(MemberTypeKey obj) | ||
{ | ||
return obj._type.GetHashCode() ^ obj._flags.GetHashCode() ^ obj._memberName.GetHashCode(); | ||
} | ||
} | ||
|
||
private class MemberTypeKeyCaseInsensitiveEqualityComparer : IEqualityComparer<MemberTypeKey> | ||
{ | ||
public static readonly MemberTypeKeyCaseInsensitiveEqualityComparer Instance = new MemberTypeKeyCaseInsensitiveEqualityComparer(); | ||
public bool Equals(MemberTypeKey x, MemberTypeKey y) | ||
{ | ||
return x._type.Equals(y._type) && x._flags == y._flags && x._memberName.Equals(y._memberName, StringComparison.OrdinalIgnoreCase); | ||
} | ||
|
||
public int GetHashCode(MemberTypeKey obj) | ||
{ | ||
return obj._type.GetHashCode() ^ obj._flags.GetHashCode() ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj._memberName); | ||
} | ||
} | ||
|
||
private readonly ParserArguments _arguments; | ||
private readonly BindingFlags _bindingCase; | ||
private readonly MemberFilter _memberFilterCase; | ||
private readonly Dictionary<MemberTypeKey, MemberInfo[]> _members; | ||
|
||
public MemberFinder(ParserArguments arguments) | ||
{ | ||
_arguments = arguments; | ||
_bindingCase = arguments.Settings.CaseInsensitive ? BindingFlags.IgnoreCase : BindingFlags.Default; | ||
_memberFilterCase = arguments.Settings.CaseInsensitive ? Type.FilterNameIgnoreCase : Type.FilterName; | ||
_members = new Dictionary<MemberTypeKey, MemberInfo[]>(arguments.Settings.CaseInsensitive ? (IEqualityComparer<MemberTypeKey>)MemberTypeKeyCaseInsensitiveEqualityComparer.Instance : MemberTypeKeyEqualityComparer.Instance); | ||
} | ||
|
||
public MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) | ||
{ | ||
var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | | ||
(staticAccess ? BindingFlags.Static : BindingFlags.Instance) | _bindingCase; | ||
|
||
foreach (var t in SelfAndBaseTypes(type)) | ||
//Not looping here because FindMembers loops internally and returns us the highest level member | ||
var members = FindMembers(type, MemberTypes.Property | MemberTypes.Field, flags, memberName); | ||
if (members.Length != 0) | ||
{ | ||
var members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, flags, _memberFilterCase, memberName); | ||
if (members.Length != 0) | ||
return members[0]; | ||
return members[0]; | ||
} | ||
return null; | ||
} | ||
|
||
private MemberInfo[] FindMembers(Type type, MemberTypes memberTypes, BindingFlags flags, string memberName) | ||
{ | ||
var key = new MemberTypeKey(type, memberName, flags); | ||
if (_members.TryGetValue(key, out var members)) | ||
{ | ||
if (members != null) | ||
{ | ||
return members; | ||
} | ||
} | ||
members = type.FindMembers(memberTypes, flags, _memberFilterCase, memberName); | ||
if (members.Length == 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For methods, you should always consider the base types. This would also solve the comment in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was implemented in this way to preserve the same results as the previous implementation. Primarily we wouldn't want to consider the base type at this location because we would end up getting ambiguous method matches in the case of "new" and "override" |
||
{ | ||
foreach (var v in SelfAndBaseTypes(type)) | ||
{ | ||
if (v == type) | ||
{ | ||
continue; | ||
} | ||
var subMembers = FindMembers(v, memberTypes, flags, memberName); | ||
if (members.Length == 0 && subMembers.Length > 0) | ||
{ | ||
//We don't break here because there is a possibility that somebody outside here is also doing an additional tree prioritization (See FindMethods) | ||
members = subMembers; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See other comment. If concatenated with different levels of hierarchy we could get ambiguous fields, properties, etc . The reason for the assign from submemebrs is that it gives the closest match. For example if somebody had 6 levels of inheritance (yes, code smell but bear with me) and we were looking for a member named Foo that was only on the base class this should allow us to jump down the inheritance without having to loop through the whole inheritance cycle. However I think my comment/lack of break is incorrect in the code. I will make a few more tests to verify that members on base types are correctly found. |
||
} | ||
} | ||
} | ||
_members[key] = members; | ||
return members; | ||
} | ||
|
||
public IList<MethodData> FindMethods(Type type, string methodName, bool staticAccess, Expression[] args) | ||
{ | ||
var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | | ||
(staticAccess ? BindingFlags.Static : BindingFlags.Instance) | _bindingCase; | ||
//Yes, FindMembers loops internally, but there are certain circumstances where we end up also needing this | ||
//An example is Type.GetMethods(), where there are multiple overloads on the base Type class | ||
//and one override on a derived class. The DeclaredOnly flag will return the single method on the derived class unless we go lower. | ||
//The existing unit tests do pass if we get rid of DeclaredOnly and remove the loop, but I'm not sure of the actual impact that may have. | ||
foreach (var t in SelfAndBaseTypes(type)) | ||
{ | ||
var members = t.FindMembers(MemberTypes.Method, flags, _memberFilterCase, methodName); | ||
var members = FindMembers(t, MemberTypes.Method, flags, methodName); | ||
var applicableMethods = MethodResolution.FindBestMethod(members.Cast<MethodBase>(), args); | ||
|
||
if (applicableMethods.Count > 0) | ||
|
@@ -100,14 +181,19 @@ private static IEnumerable<Type> SelfAndBaseTypes(Type type) | |
{ | ||
if (type.IsInterface) | ||
{ | ||
var types = new List<Type>(); | ||
AddInterface(types, type); | ||
|
||
types.Add(typeof(object)); | ||
var types = new HashSet<Type>(); | ||
foreach (var v in AddInterface(types, type)) | ||
{ | ||
yield return v; | ||
} | ||
|
||
return types; | ||
yield return typeof(object); | ||
yield break; | ||
} | ||
foreach (var t in SelfAndBaseClasses(type)) | ||
{ | ||
yield return t; | ||
} | ||
return SelfAndBaseClasses(type); | ||
} | ||
|
||
private static IEnumerable<Type> SelfAndBaseClasses(Type type) | ||
|
@@ -119,14 +205,14 @@ private static IEnumerable<Type> SelfAndBaseClasses(Type type) | |
} | ||
} | ||
|
||
private static void AddInterface(List<Type> types, Type type) | ||
private static IEnumerable<Type> AddInterface(HashSet<Type> types, Type type) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless I'm mistaken, with the current implementation, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the catch. The reason for not filling the HashSet and returning that set is that we are not 100% guaranteed that the order in which we populate the HashSet is the order in which it was added, so if it ever varied across executions we could find ourselves in an extremely difficult to replicate scenario. |
||
{ | ||
if (!types.Contains(type)) | ||
if (types.Add(type)) | ||
{ | ||
types.Add(type); | ||
foreach (var t in type.GetInterfaces()) | ||
yield return type; | ||
foreach (var i in type.GetInterfaces().SelectMany(x => AddInterface(types, x))) | ||
{ | ||
AddInterface(types, t); | ||
yield return i; | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it's worth caching the members. It's only useful if the same member is accessed multiple times in the same expression, but I'm not sure that's frequent in regular use of the Library.
I've still reviewed the proposal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, agree. Accessing the same member I suppose it is not so common.
Maybe we can think of a cache per Interpreter instance. But we should must be careful to design it, because Interpreter class can be used by multiple threads. But I suggest to put this optimization in a separate PR, to eval it separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I included this caching logic was because I found during analysis that we would frequently hit the same lookup if we were doing any non trivial expressions, particularly ones that had more than one of the same call happening, for example x + y + z, it would perform the lookup each time. If I recall correctly I think we were also hitting the same lookup more than once even on more trivial expressions like x + y but I need to verify that again.
However the major thing I found is that with how the actual lookup happens in type.FindMembers if we ever hit any lookup more than 1 time we are instantly better off for having the catching.