I have a question about delegate caching. Lets say I have this simple object:
public class MyObject { public MyObject() {} public void Speak( delegate Action<string> getString ) { Console.WriteLine( getString() ); } }
And I do this on it:
for( var i = 0; i < 1000; i++ ) myObject.Speak( () => i.ToString() );
By capturing the variable "i", it actually NEWs up 1,000 anonymous objects to store the value the "i" that gets captured by the closure at each iteration. Naturally I'd like to avoid this unnecessary allocation - why not just pass the "i" value via the stack instead?
So I do exactly that:
public class MyObject { public MyObject() {} public void Speak( int i, delegate Func<int,string> getString ) { Console.WriteLine( getString( i ) ); } }
for( var i = 0; i < 1000; i++ ) myObject.Speak( i, (closure) => closure.ToString() );
This is better. No more closures. Only one heap allocation to cache the delegate, and then the delegate gets reused. However, it's not generic, so if I want to call "speak" with something other than a single integer, I'm out of luck. So lets make it generic instead:
public class MyObject { public MyObject() {} public void Speak<C>( C closure, delegate Func<C,string> getString ) { Console.WriteLine( getString( closure) ); } }
for( var i = 0; i < 1000; i++ ) myObject.Speak( i, (closure) => closure.ToString() );
At first glance, it looks we still get to cache the delegate. OR SO IT SEEMS??? Maybe not: I came across this article: http://blogs.msdn.com/b/pfxteam/archive/2012/02/03/10263921.aspx
... and it seems that if a delegate has a generic in its signature, then in fact it will NOT cache the delegate, and instead heap allocate a brand new delegate for each call, even if it does not capture any variables! (This seems like a performance bug in the C# compiler, IMO.) Is my understanding accurate?
If so, is there a work-around to avoid these excessive heap allocations when dealing with generic delegates? Also, can I automatically detect these without a profiler? I can easily detect the creation of anonymous classes via Reflection, and this has allowed my tests to detect accidental variable capture within certain performance-critical loops, but I don't know how to detect non-cached delegates that do not capture (in the case of generic delegates).
public class MyObject { public MyObject() {} public void Speak( delegate Action<string> getString ) { Console.WriteLine( getString() ); } }
And I do this on it:
for( var i = 0; i < 1000; i++ ) myObject.Speak( () => i.ToString() );
By capturing the variable "i", it actually NEWs up 1,000 anonymous objects to store the value the "i" that gets captured by the closure at each iteration. Naturally I'd like to avoid this unnecessary allocation - why not just pass the "i" value via the stack instead?
So I do exactly that:
public class MyObject { public MyObject() {} public void Speak( int i, delegate Func<int,string> getString ) { Console.WriteLine( getString( i ) ); } }
for( var i = 0; i < 1000; i++ ) myObject.Speak( i, (closure) => closure.ToString() );
This is better. No more closures. Only one heap allocation to cache the delegate, and then the delegate gets reused. However, it's not generic, so if I want to call "speak" with something other than a single integer, I'm out of luck. So lets make it generic instead:
public class MyObject { public MyObject() {} public void Speak<C>( C closure, delegate Func<C,string> getString ) { Console.WriteLine( getString( closure) ); } }
for( var i = 0; i < 1000; i++ ) myObject.Speak( i, (closure) => closure.ToString() );
At first glance, it looks we still get to cache the delegate. OR SO IT SEEMS??? Maybe not: I came across this article: http://blogs.msdn.com/b/pfxteam/archive/2012/02/03/10263921.aspx
... and it seems that if a delegate has a generic in its signature, then in fact it will NOT cache the delegate, and instead heap allocate a brand new delegate for each call, even if it does not capture any variables! (This seems like a performance bug in the C# compiler, IMO.) Is my understanding accurate?
If so, is there a work-around to avoid these excessive heap allocations when dealing with generic delegates? Also, can I automatically detect these without a profiler? I can easily detect the creation of anonymous classes via Reflection, and this has allowed my tests to detect accidental variable capture within certain performance-critical loops, but I don't know how to detect non-cached delegates that do not capture (in the case of generic delegates).