tags:

views:

300

answers:

4

I have a framework that allows users to do queries to a specific datasource (the Football Manager 2010 ingame database, for those of you interested).

In this framework, I have two different modes wherein my framework can run: realtime and cached mode. I want users who use this framework to be able to switch by just calling a different constructor (e.g. new Context(Mode.Cached)). That should be the only switch a user should make, so he can still have all the same Linq calls, but just use Cached mode when his application fits better. Clear.

I had decided that using PostSharp should be my best choice because:

  • Create an aspect on every property (that's already been decorated by an attribute)
  • In that aspect, check whether we are in Cached or Realtime mode
  • Return the value either from memory or from cache

Well that works. BUT! Speed is not good enough. When doing the following on 90.000 objects:

foreach (Player p in fm.Players)
{
    int ca = (short)ProcessManager.ReadFromBuffer(p.OriginalBytes, PlayerOffsets.Ca, typeof(Int16));
}

It takes only 63 ms. (ReadFromBuffer is a highly optimized function which takes byte[], int, Type and returns object), 63 ms is very reasonable considering the large amounts of objects.

But! In PostSharp, I implemented quite the same using this:

    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
    {
        if (eventArgs.Method.Name.StartsWith("~get_"))
        {
            if (Global.DatabaseMode == DatabaseModeEnum.Cached)
            {
                byte[] buffer = ((BaseObject)eventArgs.Instance).OriginalBytes;

                eventArgs.ReturnValue =
                        ProcessManager.ReadFromBuffer(buffer, this.Offset, eventArgs.Method.ReturnType);
            }

Now I call this using

foreach (Player p in fm.Players)
{
    int ca = p.CA;
}

And it takes 782 ms, more than 10 times as much!

I created the aspect as:

[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, PersistMetaData = true)]
internal class FMEntityAttribute : OnMethodInvocationAspect
{
    public FMEntityAttribute(int offset, int additionalStringOffset)
    {
        this.Offset = offset;
        this.AdditionalStringOffset = additionalStringOffset;
    }
    //blah blah AOP code
}

And the property is decorated like

    [FMEntityAttribute(PlayerOffsets.Ca)]
    public Int16 CA { get; set; }

How can I get this to perform well?!

+1  A: 
  1. Use CompileTimeValidate method to check if its a property or not
Well it's always a property, as I only apply the attribute to properties. The check for ~get, is there because I also have different setter behaviours for the two modes, how'd I seperate them?
Jan Jongboom
Still you can move the if statement and string compare to CompileTimeValidate and save the result in private bool property of the aspect.
Cool, cutted it down with 70% using this
Jan Jongboom
+1  A: 

Instead of creating your context using new Context(Mode.Cached)), have a factory method which creates a context. Then implement your two behaviours in two different classes which share whatever they need of an abstract super type. Use aspects and reflection to solve problems which don't have a simple direct solution.


replace

[FMEntityAttribute(PlayerOffsets.Ca)] public Int16 CA { get; }

with

public Int16 CA { get { return PlayerAttrs.Ca.Get(this); } }

where PlayerAttrs has an operator Int16 to convert itself to Int16 on demand,has the offset required, and performs the appropriate cached/non-cached lookup.

Pete Kirkham
Yes, I did quite think about that, but there are around 400 properties seperated over 10 classes at this point. Every property behaves the same: do a call to ProcessManager.ReadFromBuffer with the byte[] of the object, the offset which is in it's attribute, and it's type. For the other mode something similar. Seems to me that Code weaving should be perfect for this, as otherwise I'd have to copy-paste the same code 400 times.
Jan Jongboom
Ok. Your provided code example seems like a really good idea. Will implement this.
Jan Jongboom
A: 

Reflection can be expensive. One thing you might try is actually compiling a wrapper for this class at runtime, and saving yourself the per-call hit that you currently have.

Chris
I'm not doing reflection
Jan Jongboom
+2  A: 

You could get much better results using PostSharp 2.0's LocationInterceptionAspect.

But then you should avoid using eventArgs.Method.ReturnType at runtime; rather get the value in method RuntimeInitialize and store it in a field. So System.Reflection is not used at runtime.

Gael Fraiteur
Ah, I thought eventArgs.Method.ReturnType was just a field created at compile time, and not used for reflection!?
Jan Jongboom