Introspection.MethodInfo.Invoke vs. Delegate.Invoke

Is there a speed difference between Introspection.MethodInfo.Invoke and Delegate.Invoke?

In my current project, I have a series of methods that need to be called in sequence. The sequence can be set by the user. These methods are very short (most are no more than about 5 lines of code). What I would like to do is set up an array of pointers to the methods, and then invoke them as in the following code:

Sub RunSequence
  for i as integer = 0 to ubound(myMethodPtrs)
    myMethodPtrs(i).invoke
  next
End Sub

The array myMethodPtrs will typically contain about 10 to 20 elements, but the RunSequence method could be called hundreds or thousands of times in a loop. So, I would like this to execute as fast as possible.

Time it :slight_smile:

I’d setup 4 buttons. 1 for introspection that lists it’s time in a TextArea. Another button for delegate. The last two buttons are repeats but with all pragmas on. Compile and click each button multiple times to see what difference there is.

Darn. I was hoping to avoid that. Unfortunately, up to this point I haven’t managed to use either method successfully. Starting with the introspection method, which is what I would prefer, this is my test code:

Public Function MyTestFunction(x As Integer) as Integer
  dim y As integer = x*x+2
  MsgBox "inner method called"
  return y
End Function

Public Sub SpeedTest()
  Dim myMethods() As Introspection.MethodInfo = Introspection.GetType(me).GetMethods
  Dim mInfo As Introspection.MethodInfo
  for i as Integer=0 to UBound(myMethods)
    if myMethods(i).Name="MyTestFunction" then
      mInfo = myMethods(i)
      MsgBox "Found MyTestFunction by introspection"
      exit for
    end if
  next
  dim testArg As Integer = 15
  dim myResult As Integer
'Everything is fine up to this point. But I don't have the right syntax in the next line
  myResult = mInfo.Invoke(self,testArg) 'I get an error on this line
End Sub

The error on the noted line is: There is more than one item with this name and it’s not clear to which this refers.

Is me and self different? You use GetType(me) then .Invoke(self,

When I try either me or self, I get the error message mentioned above.
If I use GetType(me) or GetType(self), I get a “This item does not exist.” error message.

Oh, it’s the return type. MethodInfo.Invoke returns a Variant. That should auto-convert to Integer/myResult I think, but this works

dim v As Variant v = mInfo.Invoke(self, testArg)

In a module, Me and Self mean nothing.

Seems

myResult = mInfo.Invoke(self, testArg).IntegerValue

should work but doesn’t.

Because Invoke is overloaded with the same parameters but different return types it gets confused, so you have to assign to the exact return type: Variant.

edit: err, it’s Auto now, but Variant is working… so I don’t know :stuck_out_tongue:
old http://documentation.xojo.com/index.php/MethodInfo.Invoke
new http://developer.xojo.com/xojo-introspection-methodinfo$Invoke

edit2: now “v = mInfo.Invoke(self, testArg)” isn’t working. I swear it was just minutes ago. I’m really confused, Sorry. :S

edit3: OK it worked (compiled) when no parameters are passed “v = mInfo.Invoke(self)”. Even assigning to Auto I can’t get it to work with parameters either. Always ‘more than 1 item with name’.

Thanks. I’ve changed to use variants now, and I’ve verified that it works with a return parameter as long as there are no calling parameters.
This works:
myResult = mInfo.Invoke(me)
where myResult is type variant.
So now I need to figure out how to get the input parameters to work.

I figured it out. :slight_smile:
I overlooked a small but important detail in the docs. The parameters are actually an array of variants. So this works:

  Dim myMethods() As Introspection.MethodInfo = Introspection.GetType(me).GetMethods
  Dim mInfo As Introspection.MethodInfo
  dim y As Integer
  for i as Integer=0 to UBound(myMethods)
    if myMethods(i).Name="MyTestFunction" then
      mInfo = myMethods(i)
      exit for
    end if
  next
  dim testArg As Variant = 15
  dim myResult As Variant
  dim myMethodName As String = mInfo.Name
  dim myParameters() As Variant = array(testArg)
  myResult = mInfo.Invoke(me,myParameters)
  MsgBox "result: "+str(myResult)

Now to figure out how to do this with delegates.

Here the working speed comparison test code:

  Dim myMethods() As Introspection.MethodInfo = Introspection.GetType(me).GetMethods
  Dim mInfo As Introspection.MethodInfo
  
  for i as Integer=0 to UBound(myMethods)
    if myMethods(i).Name="MyTestFunction" then
      mInfo = myMethods(i)
      exit for
    end if
  next
  dim testArg As integer = 15
  dim myVariantResult As Variant
  dim myMethodName As String = mInfo.Name
  Dim myParameters(0) As Variant
  dim elapsedTime As Double
  'Start Introspection speed test
  elapsedTime=Microseconds
  for i as Integer = 1 to 10000
    myParameters(0) = testArg
    myVariantResult = mInfo.Invoke(me,myParameters)
  next
  elapsedTime=Microseconds-elapsedTime
  MsgBox "Introspection result: "+str(elapsedTime)+" microseconds"
  
  dim myDelegate As TestDelegate
  myDelegate=AddressOf MyTestFunction
  dim myIntResult As Integer
  'Start Delegate speed test
  elapsedTime=Microseconds
  for i as Integer = 1 to 10000
    myIntResult=myDelegate.Invoke(testArg)
  next
  elapsedTime=Microseconds-elapsedTime
  MsgBox "Delegate result: "+str(elapsedTime)+" microseconds"

As I suspected, Delegates are faster:

Introspection result: 10426.6 microseconds
Delegate result: 1907.025 microseconds

But I wasn’t expecting such a huge difference.

So, now my next question is whether it is possible to assign a method address to a delegate from info obtained by introspection?

Good catch! My eyes were gliding over that assuming it was a ParamArray.

I don’t think so, there’d need to be a method on MethodInfo returning the delegate but none exists. Plus it’d have to return the delegate ‘instance’ as Variant or Auto since a generic type of Delegate is not permissible.

Thanks for the info.

I didn’t think so either, but thought that perhaps there was some remote chance of digging out a pointer from some of the introspection info and then coercing it into a delegate.

I should add that the above speed tests were run from the IDE in debug mode. For completeness I decided to see how a select/case construct for calling the methods would compare. Since I currently have 30 different methods to call, I set up 30 cases and called the 15th, assuming that this would give the average call time. Interestingly this is still faster than than the introspection.invoke method.
Select Case result: 4744.34 microseconds
falling about halfway between introspection and delegates. Of course, the coding is the ugliest.
Then I compiled to a 64 bit application with most aggressive optimization (still using 2016r3 BTW, on 2015 MacBookPro), and got these results.
Introspection result: 6272.875 microseconds
Delegate result: 405.012 microseconds
Select Case result: 438.9331 microseconds

Note that the Delegate and the Select/Case results are essentially identical, speedwise. So, it makes me wonder if delegates have any advantages over select/case for choosing which method to call. In either case, the method names have to be hard coded into the program source.

Just a thought, but if you stored the delegates in a Dictionary instead of using a Select statement then the lookup time would be constant and the code would probably be cleaner.

A dictionary would be a cleaner solution, but there’s still the problem of having to hard code the method names in the source code, which is extremely limiting.

Despite the speed difference, I’ve decided to go with the introspection solution. I think that the speed difference will be less of an issue after a bunch of other program overhead is included.

Aside from the fact that I don’t have to hard code the method names, the introspection solution provides another important advantage that makes the code much easier to maintain. When the class is instantiated, I use introspection to put together an array of the ‘invokable’ methods, as well as arrays of a number attributes that have been assigned to these methods. If I didn’t use introspection, then I’d have to build these attribute arrays some other way, and the data would be located apart from the methods they are associated with. Then, if I add or delete a method, I would also have to separately edit the attribute lists. Using introspection, everything is part of the method, and the attributes take care of themselves.

I would perfer to use Invoke with dictionary to store the functions or handlers…
method instrospetion would be slow

I’ve now completed some more realistic testing, calling one of the actual methods that I’ll be using. Only 3 of the 30 methods had an input argument, and they can retrieve this data from an existing class property. None of the methods have return values. So, I’ve been able to eliminate the variant array business. This has speeded things up considerably. It’s still slower than delegates and select/case, but it’s not such a big difference anymore.

BTW, I only use introspection when the first instance is created. It creates an array of all of the methodInfos, and those are then used to build the script arrays. So the actual calling of the methods looks like this:

Public Sub RunCommandSequence()
  for cmdPtr = 0 to CmdListSize
    CmdList(cmdPtr).Invoke(me)
  next
End Sub