1

The following cache helper takes a key name and a creator function. If the cache already contains the named object then it is returned, otherwise the creator is invoked to create the object, stick it in the cache and return the new object.

public static T GetObject<T>(String name, Func<T> creator) { ... }

Obviously the creator function will get called once (the first time), and the other 999 times will just get ignored.

What are the performance implications? If this was just a method pointer then I assume it will be quite trivial - just pushing a pointer on the stack each time - but what if the lambda is expensive, maybe a closure with state - how much work actually gets done every time we call this after the object is already created (pushing the lambda on the stack just to be ignored).

Is there an alternative strategy to this?

Etherman
  • 1,777
  • 1
  • 21
  • 34
  • 1
    Interesting question, but I doubt you'll ever notice any performance hit at all, no matter how 'expensive' your lambda is. – Alexander Derck May 24 '16 at 20:34
  • 1
    A lambda is actually syntactic sugar. The compiler will actually autogenerate a method/class (depending on the closure). Once you get to the IL it's much the same code. – Aron May 24 '16 at 23:57
  • If you want to know the performance implications profile the code and find out. You've already written the code. Run it. – Servy May 25 '16 at 00:22
  • @Aron ah, that's the part that was troubling me. So if it captures local vars then the compiler will generate it as a class, the class method is passed, and the only difference would be however many local vars get added to the autogenerated class. – Etherman May 25 '16 at 07:17
  • 1
    @Etherman actually no. A quirk of the C# compiler is that it always captures `this`, for performance. This results the "access to modified closure" warning from R#. The main difference them is that the closure requires an instantiation. – Aron May 25 '16 at 07:21

1 Answers1

2

Let's first give your method an implementation, then we can look at what's going on under the covers.

private static Dictionary<string, object> _LUT;

public static T GetObject<T>(String name, Func<T> creator)
{
    object obj;
    if (_LUT.TryGetValue(name, out obj))
    {
        return (T)obj;
    }
    T ret = creator();
    _LUT.Add(name, ret);
    return ret;
}

public string GetString(string name)
{
    return GetObject<string>(name, () => "Foo");
}

Instructions for GetObject(...):

IL_0000: ldsfld System.Collections.Generic.Dictionary`2[System.String,System.Object] _LUT
IL_0005: ldarg.0
IL_0006: ldloca.s System.Object (0)
IL_0008: callvirt Boolean TryGetValue(System.String, System.Object ByRef)
IL_000d: brfalse.s IL_0016
IL_000f: ldloc.0
IL_0010: unbox.any T
IL_0015: ret
IL_0016: ldarg.1
IL_0017: callvirt T Invoke()
IL_001c: stloc.1
IL_001d: ldsfld System.Collections.Generic.Dictionary`2[System.String,System.Object] _LUT
IL_0022: ldarg.0
IL_0023: ldloc.1
IL_0024: box T
IL_0029: callvirt Void Add(System.String, System.Object)
IL_002e: ldloc.1
IL_002f: ret

Instructions for GetString(...):

IL_0000: ldarg.1
IL_0001: ldsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_0006: brtrue.s IL_0019
IL_0008: ldnull
IL_0009: ldftn System.String <GetString>b__0()
IL_000f: newobj Void .ctor(System.Object, IntPtr)
IL_0014: stsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_0019: ldsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_001e: call System.String GetObject[String](System.String, System.Func`1[System.String])
IL_0023: ret

As you can see, your lambda is being stored in a static field of type System.Func1[System.String]

which is being passed to your method by reference (Why are delegates reference types?), and calling System.Func1[System.String].Invoke() when needed, so the size of the method should make no difference.

As for your question on whether an alternative exists:

If this is a one-time function you could try System.Lazy, if you're looking for general Caching support I suggest checking out this: .NET 4 Caching Support

Community
  • 1
  • 1
Mr Anderson
  • 2,200
  • 13
  • 23