Peter said:
I checked Google, and it had nothing to say on the topic that supports
your claim.
And, by the way, since something as easily tested as this is such a dumb
thing to even get stuck on, I wrote a small console application to test
the difference (code copied below).
The overall performance varies quite a bit depending on context: the
first time the program runs in a given context, the two methods are
practically the same, but subsequent executions show a small difference.
The virtual method implementation is only faster in one scenario:
running outside the debugger, with the debug (non-optimized) build.
The event-based implementation is faster while running in the debugger,
and more importantly is _also_ faster in the optimized build running
from the command prompt.
Of course, both indirect calls are noticeably slower than simply calling
a method directly. But even there, the difference is small. Also, I
found that code order significantly affects the performance, much more
so than the implementation details. For example, I was able to get the
event-based implementation to be MUCH faster, simply by running it after
the other two implementations. The virtual method implementation did
not enjoy a similar boost by putting it at the end.
I ran a version of the code that repeated the event and virtual-method
implementations after the direct call, and the difference was even
greater. Both the event-based and virtual method implementations
enjoyed a faster run, but the virtual-method implementation only jumped
by about 25%, while the event-based implementation jumped 75%.
The most disparate case using the code posted below was a sequence of
trials running from the command-prompt, with the optimized build, and in
that case, for a five-second test run for each implementation, I got
about 9 million iterations with the virtual method, about 10 million
iterations with the event-based implementation, and about 13 million
iterations calling the method directly.
Note that this was an unusual sequence. I refactored the code so that
the TimeSpan passed to each method was initialized once in a local
variable, rather than once per call, and that brought the iteration
count for each method closer to the average for some reason (probably a
cache issue, since the TimeSpan was of course initialized outside of any
of the code actually measuring performance). This refactored code is
what I posted here.
Other than the ordering effect I noted three paragraphs back, the actual
iteration count for most of the trials was VERY close for all
mechanisms; almost always, it came in right around 11 million iterations
in 5 seconds. The only time I was able to produce a significant
difference between the mechanisms, regardless of the type of build and
context for running, was the dramatic improvement the event-based
mechanism got the second time it was run within the same execution of
the test app, and in that case the event-based mechanism was nearly
double the performance of any other method.
Frankly, while the numbers directly contradict your claim that using
events is slower, it's still my opinion that the performance difference
is not really worth worrying about at all. If you are doing so little
in your methods that the roughly 10% difference between virtual methods
and events is important, what you really need to do is change your code
so it doesn't spend so much of its time just dealing with the overhead
of calling functions.
But if you really believe that sort of performance difference is worth
worrying about and don't want to fix the code so that calling overhead
isn't significant, you're better off using events than virtual methods.
Pete
Here's the code:
using System;
namespace TestVirtualEvent
{
class Program
{
class TestClass
{
long _iterations;
public long RunVirtual(TimeSpan tsDuration)
{
DateTime dtEnd;
_iterations = 0;
dtEnd = DateTime.Now + tsDuration;
while (DateTime.Now < dtEnd)
{
_TestVirtual();
}
return _iterations;
}
public long RunEvent(TimeSpan tsDuration)
{
DateTime dtEnd;
_iterations = 0;
_TestEvent += _TestEventHandler;
dtEnd = DateTime.Now + tsDuration;
while (DateTime.Now < dtEnd)
{
_TestEvent();
}
return _iterations;
}
public long RunDirect(TimeSpan tsDuration)
{
DateTime dtEnd;
_iterations = 0;
dtEnd = DateTime.Now + tsDuration;
while (DateTime.Now < dtEnd)
{
_TestDirect();
}
return _iterations;
}
delegate void VoidDelegate();
event VoidDelegate _TestEvent;
protected virtual void _TestVirtual()
{
_iterations++;
}
void _TestEventHandler()
{
_iterations++;
}
void _TestDirect()
{
_iterations++;
}
}
static void Main(string[] args)
{
TestClass test = new TestClass();
TimeSpan ts = new TimeSpan(0, 0, 5);
Console.WriteLine("virtual: {0} iterations",
test.RunVirtual(ts));
Console.WriteLine("event: {0} iterations",
test.RunEvent(ts));
Console.WriteLine("direct: {0} iterations",
test.RunDirect(ts));
Console.ReadLine();
}
}
}