Ayende @ Rahien

It's a girl

Reducing the cost of getting a stack trace

image I am trying to find ways to reduce the cost of the stack trace used in NH Prof. The access to the stack trace is extremely valuable, but there is a significant cost of using it, so we need a better way of handling this. I decided to run a couple of experiment running this.

All experiments were run 5,000 times, on a stack trace of 7 levels.

  • new StackTrace(true) - ~600ms
  • new StackTrace(false) - ~150ms

So right there, we have a huge cost saving, but let us continue a bit.

  • throwing exception - ~400ms

That is not so good, I have to say.

Well, when in doubt, cheat!

Using reflector and some _really_ nasty stuff, I came up with this:

var stackFrameHelperType = typeof(object).Assembly.GetType("System.Diagnostics.StackFrameHelper");
var GetStackFramesInternal = Type.GetType("System.Diagnostics.StackTrace, mscorlib").GetMethod("GetStackFramesInternal",BindingFlags.Static|BindingFlags.NonPublic);
 
var method = new DynamicMethod("GetStackTraceFast",typeof(object),new Type[0],typeof(StackTrace),true);
 
var generator = method.GetILGenerator();
generator.DeclareLocal(stackFrameHelperType);
generator.Emit(OpCodes.Ldc_I4_0);
generator.Emit(OpCodes.Ldnull);
generator.Emit(OpCodes.Newobj, stackFrameHelperType.GetConstructor(new[] { typeof(bool), typeof(Thread) }));
generator.Emit(OpCodes.Stloc_0);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ldc_I4_0);
generator.Emit(OpCodes.Ldnull);
generator.Emit(OpCodes.Call, GetStackFramesInternal);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ret);
getTheStackTrace = (Func<object>)method.CreateDelegate(typeof(Func<object>));

Calling getTheStackTrace 5000 times with depth of 7 frames is… 54ms. And now that is a horse of a different color indeed.

And the best part is, I can use the StackFrameHelper as a key into cached stack traces.

And yes, I am aware that if anyone from the CLR team is reading this, a ninja team will be dispatched to… discuss with me the notion of supported operations vs. unsupported operation.

Comments

Benny Thomas
02/16/2009 02:40 PM by
Benny Thomas

I hate internal types and method that clearly would help when extending the .net enviroment!

I almost created my own CheckedListBox in pure frustration.

configurator
02/16/2009 03:51 PM by
configurator

What do you do with the StackFrameHelper you receive?

mendicant
02/16/2009 04:15 PM by
mendicant

We have cows all over downtown calgary that are painted up like that horse. I had almost forgotten the atroscities until this post.

Stephen
02/16/2009 04:32 PM by
Stephen

Whats the trust level to invoke that method? only a lot of web hosting is medium trust.

Ayende Rahien
02/16/2009 04:56 PM by
Ayende Rahien

I can use that to get RuntimeMethodHandle, which is good enough to create a cache key.

Ayende Rahien
02/16/2009 04:58 PM by
Ayende Rahien

Stephen,

I am assuming FullTrust, but I haven't considered this.

Stephen
02/16/2009 05:42 PM by
Stephen

I don't think its a huge problem, you can probably use the old style in the event you can't use the cheat right?

Fabian Schmied
02/17/2009 09:57 AM by
Fabian Schmied

Ayende,

you can probably form a compound cache key from the rgMethodHandle field inside of the StackFrameHelper (but beware of generic methods, where the handles are not unique per specialization!). Apart from that, the names of the methods on the stack are only available via StackFrameHelper.GetMethodBase.

And once you iterate over the frames and call GetMethodBase, you're basically back to what the "official" StackTrace class does. (Minus a few ms, maybe, when you optimize it for your needs, but never a two-third reduction...) And this at the cost of using some extremely brittle code, which is bound to break sooner or later, especially if you have a heterogeneous user base. (Which, I guess, you would have with NH Prof.)

Maybe in your current use case the advantages outweigh the disadvantages, but in most cases, I think it wouldn't be worth the trouble.

Ayende Rahien
02/17/2009 11:59 AM by
Ayende Rahien

Fabian,

I am using rgMethodHandle and rgiILOffset as compound cache key, which will work just fine for my needs, since it match a location in a the source file.

Generic methods are actually not a problem for me, since I am only interested in the source line.

And since I only care about the uniqueness of the stack trace, I don't need to get GetMethodBase(), and can use the RuntimeMethodHandle

Laptop Repair
02/20/2009 02:38 AM by
Laptop Repair

Thanks for posting this kind of ideas and tips, it's an additional knowledge from what I know before, keep up the good work

Comments have been closed on this topic.