Profiling Events vs. Virtual Functions On The 360
Over the past week or so I’ve been completely reworking my collision system in order to better decouple it from other areas of code, and also make it more flexible. One part I got stuck on for a bit was deciding on the mechanism to use for notifying owners of collision components when the component collides with something. I narrowed it down to two options:
- notify owners via the ICollisionOwner interface I was using
OR
- use an Event
I was leaning more towards events because I felt their semantics naturally fit with the usage pattern I was working. If game entities want to be notified, they simply subscribe and they get notified. This seemed cleaner and easier to understand than letting each collision component have some sort of “NotifyOwner” flag, and then call a virtual function if the flag was true. However I was a little worried about performance…I hadn’t really used delegates on the 360 before and I wanted to make sure that the overhead wasn’t going to be something astronomical before proceeding. So I set up a simple test harness that vaguely resembled how I was going to use events:
public delegate void EventDelegate(object sender, ref Vector3 parameter);
public class EventServer
{
public event EventDelegate SomeEvent;
public void RaiseEvent()
{
Vector3 param = new Vector3();
if (SomeEvent != null)
SomeEvent(this, ref param);
//for (int i = 0; i < Handlers.Count; i++)
//{
// if (Handlers[i].HandlesEvent)
// Handlers[i].HandleEventVirtual(this, ref param);
//}
}
public List<IEventHandler> Handlers = new List<IEventHandler>();
}
public interface IEventHandler
{
void HandleEventVirtual(object sender, ref Vector3 parameter);
bool HandlesEvent
{
get;
}
}
public class EventHandler : IEventHandler
{
EventServer server;
bool handleEvent;
public EventHandler(EventServer server, bool handleEvent)
{
this.server = server;
this.handleEvent = handleEvent;
if (handleEvent)
server.SomeEvent += new EventDelegate(HandleEvent);
}
void HandleEvent(object sender, ref Vector3 parameter)
{
parameter.Y += 0.001f;
}
public virtual void HandleEventVirtual(object sender, ref Vector3 parameter)
{
parameter.X += 0.001f;
}
public bool HandlesEvent
{
get { return handleEvent; }
}
}
public class EventHandler2 : EventHandler
{
public EventHandler2(EventServer server, bool handleEvent)
: base(server, handleEvent)
{
}
public override void HandleEventVirtual(object sender, ref Vector3 parameter)
{
base.HandleEventVirtual(sender, ref parameter);
parameter.Normalize();
}
}
This is a pretty simple set up: a class that will dole out events to a collection of handlers, with a derivative of the handler class also being thrown in just to make sure the compiler doesn’t do anything funky that will prevent us from actually getting virtual functions. To test events we leave it like this, to test virtual functions we comment out the event invocation and use the virtual function call instead. Any .NET junkies might notice I’ve violated the guidelines for creating custom event handlers by not using a an EventArgs derivate…the reason why is because EventArgs in a class, so creating a new instance would generate garbage everytime the event fires. And as we all know..the GC is not our friend on the Xbox.
I set it up to run with various amounts of event handlers distributed across various amounts of event servers. I then set up the game class to fire off all the event servers in the Update function and use a Stopwatch to time how long it took. I also averaged the timing results across 64 frames to smooth out the results. This is what I got:
50:1 9
22
500:1 710
220
5000:1 163000 (3.26ms)
2200
5000:10 18600
2200
5000:100 1000
2200
5000:1000 820
2200
The table shows the EventHandler:EventServer ratio, and on the right is the of time taken for invocation (in ticks). The number on top is from using Events, the bottom from using virtual functions. The first few results are pretty interesting: the virtual function method scales linearly with the amount of handlers we have, while the the time required for firing events goes up exponentially. The bottom half of the results are even more interesting: the time taken goes way down as we start to distribute the handlers more evenly across servers. In fact it goes down so much, it becomes quicker than virtual functions!. Crazy.
Anyway I had my answer: events would be fine with my setup. I can’t foresee any reason why more than one handler would subscribe to the same collision component, and even if it did the overhead is basically miniscule for the numbers I’ll be working with. But it’s always fun to experiment, right?
Comments:
Scrolls from the past: Profiling « Sgt. Conker -
[…] Over a year later, bittermanandy was writing a chronicle about what recipes and rituals he found most useful in his delvings into XNA, and in one of these chronicles, he touched on the subject of the CLR Profiler again. That same year, jwatte gave away a free potion that could be used for performing the profiling ritual in the kingdom of the Xbox 360, which was not possible using NProf. MJP made a comparison between events and virtual functions, using the Stopwatch spell, and left his writing for all to read in one of his scrolls. […]