dotnet: Performance degradation of StackFrameHelper that's shipped with 4.7.1

Run this code

public static void Main()
{
   for (int i = 0; i < 10000; i++)
   {
      new StackTrace(true).GetFrames();
   }
}

on Windows10 with .net 4.7.1 and it will take 5 seconds to complete. Run this code on the same system without 4.7.1 and it will take 200ms

There is performance degradation in System.Diagnostrics.StackFrameHelper.InitializeSourceInfo

image

Looks like caching is broken and pdb files are loaded every time image

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 17
  • Comments: 33 (18 by maintainers)

Commits related to this issue

Most upvoted comments

I was able to verify that both of those caching issues mattered and changing them brings the benchmark down from ~6000ms -> ~350ms on my machine. After I get some sleep, tomorrow I’ll see what I can learn about the remaining perf discrepancy.

I just hope it’s possible to enable the new format without breaking old use cases…

That’s certainly our goal as well. We strive for a high bar on back compat in the desktop .NET framework and we clearly fell short on this one. I’ll do my best to make it right.

We do have a fix for 4.7.1, and it will be available in a broad release soon. Likely in the January time frame.

It greatly affects unit test projects that use Moq framework. Internally, moq references the StackTrace class https://github.com/moq/moq4/blob/master/Source/MethodCall.cs#L190

Further investigation showed that of the remaining 350ms, about 110ms comes from CAS asserts added to the implementation in 4.7.1, 40ms from one time costs jitting + once-per-module costs checking for portable PDB, and then the original 200ms are carried over from the 4.7 implementation. I’m optimistic the CAS costs can be significantly reduced and the jitting costs should go away once my test scenario is NGEN’ed (the framework is automatically NGEN’ed when you receive it via Windows update or MSI installer). The once-per-module cost checking for the portable PDB would be hard to reduce without getting rid of Portable PDB support.

At this point I think the performance issues are reasonably understood and focus is shifting to constructing the best fix and the logistics of distribution. Thanks for your patience and I’ll continue to update the thread as I learn more.

Nothing specific to report, just wanted to confirm that progress is very much ongoing.

Hi

The patches have been released – here is a link to the announcement blog:

https://blogs.msdn.microsoft.com/dotnet/2018/01/09/net-framework-4-7-1-is-available-on-windows-update-wsus-and-mu-catalog/

Thanks, Alex

In 4.7.2 we were more cautious with how the portable pdb stack trace functionality is enabled. To turn it on your application either needs to target 4.7.2 (aka set 4.7.2 as the framework target when you build) or you can opt-in to the functionality with an app config switch: https://github.com/Microsoft/dotnet/blob/master/Documentation/compatibility/Stack-traces-obtained-when-using-portable-PDBs-now-include-source-file-and-line-information-if-requested.md

Hopefully one of those options gets it working for you, but if not please let us know. HTH, -Noah

Can anyone explain when to use them?

Hi @springy76! Some general information about them can be found here: https://github.com/dotnet/core/blob/master/Documentation/diagnostics/portable_pdb.md Generally the situation where you need to use them is when you are doing .Net Core or Mono development on Linux or OSX and you want nice stack traces with source line information for your debugging. If you have an app that will only run on .Net Framework for Windows they are not necessary. We are trying to support them in .Net Framework so that eventually you could use one common format for everything rather than having to pick a different option on each OS.

Or why when using them, new StackTrace(true) does not provide any line numbers?

Apologies the messaging didn’t get updated in a timely enough manner. You probably weren’t doing anything wrong but unfortunately to fix the performance issue reported here in the short term we had to disable the portable PDB feature in 4.7.1. We aim to bring it back performantly in 4.7.2. This outcome wasn’t our first choice and we were trying to fix the performance issue right away while leaving the portable pdb functionality intact. Sadly complications were discovered late in our patch testing process and we had to make a call to disable the portable pdb feature. Assuming you have the patch Alex refers to above then not seeing any source paths is now the expected behavior for Portable PDBs in 4.7.1.

Hopefully that makes sense and let me know if I can help with more questions.

Is the timeframe for this fix still January? I’m seeing some really terrible test performance and need to get to a solution soon.