Ayende @ Rahien

Hi!
My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 5,969 | Comments: 44,490

filter by tags archive

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

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

What do you do with the StackFrameHelper you receive?

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

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

Ayende Rahien

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

Ayende Rahien

Stephen,

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

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

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

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

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

Comment preview

Comments have been closed on this topic.

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. Production postmortem (5):
    29 Jul 2015 - The evil licensing code
  2. Career planning (6):
    24 Jul 2015 - The immortal choices aren't
  3. API Design (7):
    20 Jul 2015 - We’ll let the users sort it out
  4. What is new in RavenDB 3.5 (3):
    15 Jul 2015 - Exploring data in the dark
  5. The RavenDB Comic Strip (3):
    28 May 2015 - Part III – High availability & sleeping soundly
View all series

RECENT COMMENTS

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats