Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding boxing in generic blackboard

Tags:

c#

generics

A blackboard is an object that stores and fetches generic key-value pairs at runtime. It could be implemented by a Dictionary<object,object>. Some subsystem writes to the blackboard to let other subsystems read from it.

The system storing the blackboard has no idea what type of object is in it, and when. The writers and readers to a particular key always know and agree on the type of the key-value pair. Compile-time checking is sacrificed for ease of implementation - there are numerous writers and readers and they are constantly iterated on.

My blackboard interface looks like this:

interface Blackboard {
    bool HasKey(object key);
    T GetValue<T>(object key);
}

Writers create and return blackboards, so SetValue(key, value) can be an implementation detail.

My initial implementation used Dictionary<object,object> and everything was fine. However, this blackboard must be quick and alloc-free. This is non-negotiable. If a writer pushes a float value into the blackboard, the naive implementation boxes the int to put into the dictionary.

I can't use a generic type on the implementation class, BlackboardImpl<ValueType> : Blackboard, because the value type is not constant across blackboards.

I can use multiple internal dictionaries, Dictionary<object,float>, Dictionary<object,int> etc with a fallback Dictionary<object,object>, and many SetValue functions, so now I don't box on insertion. However since the GetValue function comes from the interface, I can't put constraints on it, so I'm still boxing on exit:

T GetValue<T>(object key) {
    if (typeof(T) == typeof(int)) {
        // return intStore[key]; // doesn't compile
        return (T)(object)intStore[key]; // boxes, allocates, bad.
    }
    // ...
}

Is there any syntactical trick I'm missing here, including changing the blackboard interface, to avoid boxing? Any reflection hacks are going to violate the "quick" requirement, even if you can implement it without allocations.

Cheers.

like image 941
tenpn Avatar asked Oct 01 '22 13:10

tenpn


1 Answers

While I wouldn't want to do this (and I'd need persuading that the cost of boxing is really going to be significant), you could have your multiple stores and have a variable of type Dictionary<object, T> in your method - that way I believe you'd avoid boxing:

T GetValue<T>(object key)
{
    Dictionary<object, T> store;
    if (typeof(T) == typeof(int)
    {
         store = (Dictionary<object, T>) (object) intStore;
    }
    else if (typeof(T) == typeof(float))
    {
        store = (Dictionary<object, T>) (object) floatStore;
    }

    // etc - with a default or an error case for unhandled types.
    return store[key];
}

Note that the double cast here is necessary to keep the compiler happy, but it doesn't involve boxing.

like image 74
Jon Skeet Avatar answered Oct 06 '22 02:10

Jon Skeet