Hi guys
Of late i have been getting right down into CLSA and looking at exactly what makes it up and how it works. As a result of this it is quite evident that MethodCaller get quite a work out and if this process could be refined a little further maybe it might help a fair bit. Hence I came up with the following. PLEASE NOTE: you will notice that the structure has changed a little to allow for the caching and an extra try catch statement has been added to prevent a posible AmbiguousMatchException that could occur but in essence the rest is the same. Also just for testing purposes I have not included some of the Resources calls and some of the other methods. Also this has NOT been fully tested, due to my current commitments I only got it compiling for the moment, but i wanted to see what others thought.
Thanks
Anthony
public static class MethodHelper
{
#region Private Fields
private static readonly Dictionary<string, MethodInfo> _GlobalMethodInfoCount = new Dictionary<string, MethodInfo>();
private static readonly Dictionary<string, Dictionary<Type[], MethodInfo>> _GlobalMethodInfoType = new Dictionary<string, Dictionary<Type[], MethodInfo>>();
private static readonly Dictionary<string, MethodInfo> _GlobalMethodInfoTest = new Dictionary<string, MethodInfo>();
private const BindingFlags _AllLevelFlags = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags _OneLevelFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
#endregion
#region Private Methods
#region General
private static Type GetObjectType(object criteria)
{
return criteria.GetType().DeclaringType;
}
#endregion
#region Derive Method By
private static MethodInfo DeriveMethodByLoopCount(MethodInfo[] methods, string methodName, int currentParamLength)
{
MethodInfo result = null;
foreach (MethodInfo method in methods)
{
if (method.Name == methodName && method.GetParameters().Length == currentParamLength)
{
result = method;
break;
}
}
return result;
}
private static MethodInfo DeriveMethodByTest(Type objectType, string methodName, params object[] parameters)
{
MethodInfo result = null;
//It is posible for more than one result to be found
try
{
result = objectType.GetMethod(methodName, MethodHelper._AllLevelFlags);
}
catch (AmbiguousMatchException)
{
result = MethodHelper.DeriveMethodByLoopCount(objectType.GetMethods(), methodName, parameters.Length);
if (result == null)
throw;
}
return result;
}
private static MethodInfo DeriveMethodByType(Type currentObjectType, string methodName, Type[] paramTypes)
{
//Finds the relevent method
MethodInfo result = null;
do
{
//Find for a strongly typed match
result = currentObjectType.GetMethod(methodName, MethodHelper._OneLevelFlags, null, paramTypes, null);
if (result != null)
break; //Match found
currentObjectType = currentObjectType.BaseType;
} while (currentObjectType != null);
return result;
}
private static MethodInfo DeriveMethodByCount(Type currentObjectType, string methodName, int parameterCount)
{
//Walk up the inheritance hierarchy looking for a method with the right number of parameters
MethodInfo result = null;
Type currentType = currentObjectType;
do
{
//It is posible for more than one result to be found
try
{
result = currentType.GetMethod(methodName, MethodHelper._OneLevelFlags);
if (result != null && result.GetParameters().Length == parameterCount)
break; //Match found
}
catch (AmbiguousMatchException)
{
result = MethodHelper.DeriveMethodByLoopCount(currentObjectType.GetMethods(MethodHelper._OneLevelFlags), methodName, parameterCount);
if (result == null)
throw;
}
currentType = currentType.BaseType;
} while (currentType != null);
return result;
}
#endregion
#region Retreive Cached Method By
private static MethodInfo RetreiveCachedMethodByType(Type currentObjectType, string methodName, Type[] paramTypes)
{
string fullMethodName = currentObjectType.GetType().FullName + "." + methodName + "();";
bool containsMethodKey = false;
//Brings back the correct methodName for the type if it exists
if (MethodHelper._GlobalMethodInfoType.ContainsKey(fullMethodName))
{
if (MethodHelper._GlobalMethodInfoType[fullMethodName].ContainsKey(paramTypes))
return MethodHelper._GlobalMethodInfoType[fullMethodName][paramTypes];
containsMethodKey = true;
}
//Pulls out the dictionary for the current method if it exists
Dictionary<Type[], MethodInfo> methodDictionary;
if (containsMethodKey)
methodDictionary = MethodHelper._GlobalMethodInfoType[fullMethodName];
else
{
methodDictionary = new Dictionary<Type[], MethodInfo>();
MethodHelper._GlobalMethodInfoType.Add(fullMethodName, methodDictionary);
}
//Gets method out if it can be found
MethodInfo result = MethodHelper.DeriveMethodByType(currentObjectType, methodName, paramTypes);
//Adds in the methodName info into the dictionary caching it
methodDictionary.Add(paramTypes, result);
return result;
}
private static MethodInfo RetreiveCachedMethodByCount(Type currentObjectType, string methodName, int parameterCount)
{
string fullMethodName = currentObjectType.GetType().FullName + "." + methodName + "()_" + parameterCount + ";";
if (MethodHelper._GlobalMethodInfoCount.ContainsKey(fullMethodName))
return MethodHelper._GlobalMethodInfoCount[fullMethodName];
//Gets method out if it can be found
MethodInfo result = MethodHelper.DeriveMethodByCount(currentObjectType, methodName, parameterCount);
//Adds in the methodName info into the dictionary caching it
MethodHelper._GlobalMethodInfoCount.Add(fullMethodName, result);
return result;
}
private static MethodInfo RetreiveCachedMethodByTest(Type currentObjectType, string methodName, params object[] parameters)
{
string fullMethodName = currentObjectType.GetType().FullName + "." + methodName + "();";
if (MethodHelper._GlobalMethodInfoTest.ContainsKey(fullMethodName))
return MethodHelper._GlobalMethodInfoTest[fullMethodName];
//Gets method out if it can be found
MethodInfo result = MethodHelper.DeriveMethodByTest(currentObjectType, methodName, parameters);
//Adds in the methodName info into the dictionary caching it
MethodHelper._GlobalMethodInfoTest.Add(fullMethodName, result);
return result;
}
#endregion
#endregion
#region Public Methods
public static object CallMethodIfImplemented(object currentObject, string methodName, params object[] parameters)
{
MethodInfo info = GetMethod(currentObject.GetType(), methodName, parameters);
if (info != null)
return CallMethod(currentObject, info, parameters);
else
return null;
}
public static object CallMethod(object currentObject, string methodName, params object[] parameters)
{
MethodInfo info = GetMethod(currentObject.GetType(), methodName, parameters);
if (info == null)
throw new NotImplementedException(methodName + " Method Not Implemented");
return CallMethod(currentObject, info, parameters);
}
public static object CallMethod(object currentObject, MethodInfo info, params object[] parameters)
{
//Call a private methodName on the object
object result;
try
{
result = info.Invoke(currentObject, parameters);
}
catch (Exception ex)
{
throw new CallMethodException(info.Name + " Method Call Failed", ex.InnerException);
}
return result;
}
public static MethodInfo GetMethod(Type objectType, string methodName, params object[] parameters)
{
MethodInfo result = null;
//Try to find a strongly typed match
result = MethodHelper.FindMethodByType(objectType, methodName, TypeHelper.GetParameterTypes(parameters));
//With the right number of parameters
if (result == null)
result = MethodHelper.FindMethodByCount(objectType, methodName, parameters.Length);
//No strongly typed match found, get default
if (result == null)
result = MethodHelper.FindMethodByTest(objectType, methodName, parameters.Length);
return result;
}
public static MethodInfo FindMethodByType(Type currentObjectType, string methodName, Type[] paramTypes)
{
return MethodHelper.RetreiveCachedMethodByType(currentObjectType, methodName, paramTypes);
}
public static MethodInfo FindMethodByCount(Type currentObjectType, string methodName, int parameterCount)
{
return MethodHelper.RetreiveCachedMethodByCount(currentObjectType, methodName, parameterCount);
}
public static MethodInfo FindMethodByTest(Type currentObjectType, string methodName, params object[] parameters)
{
return MethodHelper.RetreiveCachedMethodByTest(currentObjectType, methodName, parameters);
}
#endregion
}
Hi Ricky
The LinFu framework sounds interesting enough and I particularly like the idea of the FussyFinder to help find the best match, but I guess it is a fairly big deviartion from what the methodcaller currently does. Hence I was trying to fix that bug I mentioned and implement the caching.
The only thing I did find after using it a little is that the after using it and doing a little bit more testing the Dictionary<string, Dictionary<Type[], MethodInfo>> doesn't quite work the wway I had hoped and hence it probably needs to change to something like this.
private static readonly Dictionary<string, Dictionary<string, MethodInfo>> _GlobalMethodInfoType = new Dictionary<string, Dictionary<string, MethodInfo>>();
private static string ConvertToString(Type[] types)
{
StringBuilder format = new StringBuilder(";");
foreach (Type type in types)
format.Append(type.FullName + ";");
return format.ToString();
}
private static MethodInfo RetreiveCachedMethodByType(Type currentObjectType, string methodName, Type[] paramTypes)
{
string fullMethodName = currentObjectType.GetType().FullName + "." + methodName + "();";
bool containsMethodKey = false;
string paramTypesString = MethodHelper.ConvertToString(paramTypes);
//Brings back the correct methodName for the type if it exists
if (MethodHelper._GlobalMethodInfoType.ContainsKey(fullMethodName))
{
if (MethodHelper._GlobalMethodInfoType[fullMethodName].ContainsKey(paramTypesString))
return MethodHelper._GlobalMethodInfoType[fullMethodName][paramTypesString];
containsMethodKey = true;
}
//Pulls out the dictionary for the current method if it exists
Dictionary<string, MethodInfo> methodDictionary;
if (containsMethodKey)
methodDictionary = MethodHelper._GlobalMethodInfoType[fullMethodName];
else
{
methodDictionary = new Dictionary<string, MethodInfo>();
MethodHelper._GlobalMethodInfoType.Add(fullMethodName, methodDictionary);
}
//Gets method out if it can be found
MethodInfo result = MethodHelper.DeriveMethodByType(currentObjectType, methodName, paramTypes);
//Adds in the methodName info into the dictionary caching it
methodDictionary.Add(paramTypesString, result);
return result;
}
Have you done comparative performance tests using both the old and new code?
Thanks Andy, that is an interesting article.
After spending yesterday (and last night) messing with MethodCaller to get it to support ParamArray/params parameters on target methods, I am left wondering if the technique in the article will work in the general case.
I now fully appreciate just how cool the VB runtime's late binding support really is. I knew it was cool, but given all the nasty edge cases (which I don't solve btw), they obviously have some impressive code there...
I had thought about doing something like this for 3.5, but now I don't know. I don't know that I have another 16-20 hours of time to sink into that one class... I appreciate your offer of help, and (if you'll sign the contributor agreement) will accept it gladly! My deadline for 3.5 to be feature complete is Jan 25 though, so time is somewhat short I'm afraid.
Someone in the thread mentioned that they'd fixed a bug in MethodCaller? Can you be specific? In particular, what does the target method signature look like and what is passed from the caller?
Hi Rocky,
That was me who said that i think i have found a potential bug.
In the public static MethodInfo FindMethod(Type objType, string method, int parameterCount) method, it does a loop looking for methods that have a set number of paramerters. Within the loop it uses this MethodInfo info = currentType.GetMethod(method, oneLevelFlags); line which calls the GetMethod method. As far as i can see anywhere the GetMethod is used it has the ability to throw an AmbiguousMatchException exectipion. Now when you are doing calling this public static MethodInfo FindMethod(Type objType, string method, Type[] types) method, you should never get this because you can't compile with two methods of the same signature, but with the count version there is the possibility that it could. Hence why i thought that this is a bug and should have a try catch around it. You will see in code that i provided that it checks for this case and it the exception occurs runs the pretty much the same code that you have in the only AmbiguousMatchException expectation check at the moment. It probably has not arisen as a bug in many cases thus far because i imagine that the first findmethod would pick up most cases.
As far as a performance testing goes, I don’t really have the setup to be able to give you figures on that one, but what i do know is that there is a lot of looping that occurs to find the correct method and this looping logic, given a type, method name and a set of params is always going to produce the same result. Hence is there need to conduct that looping and reflection every time. Hence why i was trying to cache the results. I guess you now have a dictionary lookup and a lock to deal with if the method isn't found in that type but for the vast majority of cases i would imagine that there would have to be some tangible performance improvement.
Anthony
Note, the version of the code that i provided doesn't contain locks where it probably should. So if you want a version with locks if you are interested just contact me.
On another note, i took a look at the article that was mentioned and had a bit of a play around with it. Firstly it was evident was that where it gets most of its performance gain from (in terms of the figures it shows) is the fact that the delegate is being stored in a static variable. ajj3085 i am sure that you did receive a performance boost but i am guessing that you where storing the delegate in a static variable. If you had of done this same thing exact stored the methodinfo in the static variable i wonder if you would have noticed as bigger difference (which on the side is what I am proposing with the new methodcaller).
In any case it would seem that there is a performance to be had with the quoted method but when the methodinfo is cached the difference is a lot less pronounced. But that said, there is no reason why the new improved methodcaller couldn't cache both the methodinfo and the delegate if is perceived that including the delegate creation and storage provides a large enough performance boost to be included. If this is the case i thing it would be a relatively simple process to include this into the new methodcaller that i put forward. Although i don’t think i would be able to give you the final say on the performance difference, as most of the testing i have done performance wise has been hard to judge because it seems that within a cretin time period on a given thread .net does some caching on its own, so a test case would probably be have to be thought out.
Thanks
Anthony
I don’t disagree that there should be a performance
benefit – that’s the hypothesis. But the proof is always in the pudding, and
time grows terribly short for anything new to change in 3.5 and I may or may
not have time to do something like this before the cutoff. Time will tell.
Rocky
From: vdhant
[mailto:cslanet@lhotka.net]
Sent: Tuesday, January 08, 2008 6:37 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] MethodCaller: Possible Big Performance Boost??
On
another note, i took a look at the article that was mentioned and had a bit of
a play around with it. Firstly it was evident was that where it gets most of
its performance gain from (in terms of the figures it shows) is the fact that
the delegate is being stored in a static variable. ajj3085 i am sure that you did receive a
performance boost but i am guessing that you where storing the delegate in a
static variable. If you had of done this same thing exact stored the methodinfo
in the static variable i wonder if you would have noticed as bigger difference
(which on the side is what I am proposing with the new methodcaller).
In any case it would seem that there is a performance to be had with the quoted
method but when the methodinfo is cached the difference is a lot less
pronounced. But that said, there is no reason why the new improved methodcaller
couldn't cache both the methodinfo and the delegate if is perceived that
including the delegate creation and storage provides a large enough performance
boost to be included. If this is the case i thing it would be a relatively
simple process to include this into the new methodcaller that i put forward.
Although i don’t think i would be able to give you the final say on the
performance difference, as most of the testing i have done performance wise has
been hard to judge because it seems that within a cretin time period on a given
thread .net does some caching on its own, so a test case would probably be have
to be thought out.
Thanks
Anthony
That’s fine. As i said off the top of my head I cannot think of practical test that would be able to isolate the difference. Because if you simply run the two through a 10000 rep loop the cached version comes out slower because of the dictionary lookups and the fact that .net caches the results on the thread as I said before. So I don’t think that a simple loop like this is a very good test. In a real situation where it is happening across multiple threads and multiple user over a period of time this is where i see the difference. But that is a lot harder to test...
In any case i just thought i would share and see what people thought.
Anthony
FWIW, here's my 2 cents.
Although I can see where you are going with this one, I'm always wary of someone claiming a "big performance boost" in this way. I don't doubt the hypothesis, because it makes sense. What I doubt is the investment required in coding, testing and proving the hypothesis in terms of the man-hours of development time needed. Is it really going to give me a real "bang-for-my-buck" performance increase? Will it reduce minutes of processing time down to just seconds? Or are we talking about fractions of seconds being shaved off an already low number of seconds?
As performance is such a subjective subject, only a fully benchmarked solution makes any meaningful sense. And I agree that it is not easy to see how you can benchmark this kind of stuff. There are so many other factors that could come into play.
And are we talking theorhetical performance here, as opposed to real-life performance when the application is deployed in an operational environment and runs like a dog?
With the cost of hardware (CPUs / RAM / hard drives) being dirt cheap compared to the cost of development, the best solution to "perceived performance issues" can quite often be a hardware one, not a software one.
Don't get me wrong, if you're trying to make the best possible framework that was ever developed, then go for it and look for performance improvements everywhere.
But always remember that your customer (the business) will be more concerned that the application actually works, not how "quick" it is. An application with no bugs that's runs "slow" and can be used in a production environment to do the job is better than an application with bugs that runs "quick" but can't be deployed.
Well that's usually true as far as business people are concerned!
DavidDilworth:And are we talking theorhetical performance here, as opposed to real-life performance when the application is deployed in an operational environment and runs like a dog?
I’d like to see a test case that causes failure before solving a
theoretical problem.
In other words, if there’s some combination of declared methods
that can make the failure occur I’ll put it into my MethodCallerTest app as a
test and that way we can establish that the solution directly solves the issue.
Rocky
From: vdhant
[mailto:cslanet@lhotka.net]
Sent: Tuesday, January 08, 2008 5:27 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] MethodCaller: Possible Big Performance Boost??
Hi Rocky,
That was me who said that i think i have found a potential bug.
In the public static MethodInfo FindMethod(Type objType, string method, int
parameterCount) method, it does a loop looking for methods that have a set
number of paramerters. Within the loop it uses this MethodInfo info =
currentType.GetMethod(method, oneLevelFlags); line which calls the
GetMethod method. As far as i can see anywhere the GetMethod is used it has the
ability to throw an AmbiguousMatchException exectipion. Now when you are doing
calling this public static MethodInfo FindMethod(Type objType, string method,
Type[] types) method, you should never get this because you can't compile with
two methods of the same signature, but with the count version ther e is the
possibility that it could. Hence why i thought that this is a bug and should have
a try catch around it. You will see in code that i provided that it checks for
this case and it the exception occurs runs the pretty much the same code that
you have in the only AmbiguousMatchException expectation check at the moment.
It probably has not arisen as a bug in many cases thus far because i imagine
that the first findmethod would pick up most cases.
As far as a performance testing goes, I don’t really have the setup to be
able to give you figures on that one, but what i do know is that there is a lot
of looping that occurs to find the correct method and this looping logic, given
a type, method name and a set of params is always going to produce the same
result. Hence is there need to conduct that looping and reflection every time.
Hence why i was trying to cache the results. I guess you now have a dictionary
lookup and a lock to deal with if the method isn't found in that type but for
the vast majority of cases i would imagine that there would have to be some
tangible performance improvement.
Anthony
Ricky Supit and I have been working on this for the past 10 days
or so. Ricky did a lot of research work and put together some very nice dynamic
method code that was able to do everything MethodCaller requires. I
subsequently merged that into CSLA itself.
The result looks pretty decent, though we’re still
tweaking a bit. You can see what it looks like in svn in the C# trunk – check
out Csla.Reflection (the new home for all this stuff).
This became a high priority once I started doing some serious
perf testing on 3.5. The child object support in the data portal (which is a
really big deal in terms of reducing code and making it consistent) would have
been a perf killer without these dynamic method enhancements. With these
enhancements there’s still a perf hit, but my guess is that most people
will accept the hit to get the code savings and consistency. For those that
really need optimal performance, the manual child techniques from CSLA .NET
1.x/2.x/3.0 still work of course.
Rocky
From: ajj3085
[mailto:cslanet@lhotka.net]
Sent: Tuesday, January 08, 2008 7:30 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] MethodCaller: Possible Big Performance Boost??
There's another possiblity for speeding up reflection base
method calls. I actually implemented it in my DAL when I had to add some
more MethodInfo calls. The technique can be found here.
What you do is basically do the reflection once, then compile the delegate and
store it in a static field.
I haven't done any testing, but after I added the feature that required the
MethodInfo call, my unit tests ran noticibly slower. I'm talking from a
few seconds to about a minute and a half to run all the tests.
Implementing the method in the link above brought performance back to the few
second range.
If you like, I can try this in Csla. Just tell me how you would like
things timed. I can't promise I'll get to it soon though. ">
Andy
Copyright (c) Marimer LLC