三个星期之前我向大家求助说,VS的Profiler分析程序性能时无法跟踪框架内部的方法调用。当时我做了不少尝试,例如下载并配置了.NET Framework的symbol文件和源代码,还尝试使用了ANTS Profiler和CLR Profiler等其他工具,最终还是没有成功。Ivony...老大在评论中告诉我说Sampling方式可以获得比Instrumentation更多的信息,不过我觉得Sampling得到的结果并不像我的目标那样干净,因此还是在寻找Instrumentation的方式。不过最终耗费了一个GTSC的支持点数,才被告知——的确应该使用Sampling。
那么我现在就来详细描述一下Profiler的使用方式吧。
首先,我们准备一个Console项目ProfilerSample,其中放入一段测试代码:
点此显示
点此隐藏 static void Main(string[] args) { var array = Enumerable.Range(1, 5).Select(i => new String((char)(i + 97), 5)).ToArray(); TestConvert(array); TestParse(array); TestTryParse(array); } private static List<Int32> TestParse(String[] strings) { Int32 intValue; List<Int32> intList = new List<int>(); for (int i = 0; i < 100000; i++) { intList.Clear(); foreach (String str in strings) { try { intValue = Int32.Parse(str); intList.Add(intValue); } catch (System.ArgumentException) { } catch (System.FormatException) { } catch (System.OverflowException) { } } } return intList; } private static List<Int32> TestTryParse(String[] strings) { Int32 intValue; List<Int32> intList = new List<int>(); Boolean ret; for (int i = 0; i < 100000; i++) { intList.Clear(); foreach (String str in strings) { ret = Int32.TryParse(str, out intValue); if (ret) { intList.Add(intValue); } } } return intList; } private static List<Int32> TestConvert(String[] strings) { Int32 intValue; List<Int32> intList = new List<int>(); for (int i = 0; i < 100000; i++) { intList.Clear(); foreach (String str in strings) { try { intValue = Convert.ToInt32(str); intList.Add(intValue); } catch (System.FormatException) { } catch (System.OverflowException) { } } } return intList; }
然后在菜单里选择Analyze - Profiler - New Performance Session,并将ProfilerSample加入Targets中:
请注意这里的Profile方式是Sampling。于是我们Profile这个程序(ProfilerSample),将会得到一个报表,于是我们可以浏览它的Call Tree:
这里显示的,已经是Expand All后的结果,可以看到调用非常之多。至于Column,除了Function Name之外,我只保留了“Inclusive Samples”和“Inclusive Samples %”两列,它们表示有多少次采样是落在这个方法的“调用树”上的。当然,您还可以选择Exlusive Samples等列,它与Inclusive的区别在于,Exlusive Samples不包括落在当前方法“子过程”里的采样。
此外,如果您看到的结果并不像截图里那么多,可能是由于Noise Reduction Options的设置关系。您可以点击上图工具栏中最右侧的按钮进行设置。
但是,看着这么一大堆信息我们会无从下手。因此,我们将滚动条拖到中段,可以发现ProfilerSample.Program.Main方法。对它点击右键,选择Set Root便会将其设为树装图的根。再折叠一些意义不大的方法(如mscorwks.dll),我们可以得到这样的结果:
经过观察,可以知道哪个方法,包括框架里的方法消耗的时间比较长。至于Sampling的细节,例如采样的频率,您可以在Performance Session的属性中进行设置。例如,可以选择不同的时钟周期长短进行采样。一般来说,采样频率越高,结果越准确,但这也意味着采样本身可能就会占用较多的时间——还好我们观察的主要是“比例”而不是绝对数量。
不过话说回来,我认为这个做法的目的还是在于观察框架“内部实现”的性能,有点探究“黑盒”的意味。在实际使用过程中,我们可能还是使用Instrumentations为好,因为这样得到的结果比较“干净”:
虽然信息较Sampling为少,但是对于我们进行性能优化来说已经足够了。因为此时结果里显示的都是我们自己写的方法,这也是我们唯一可以修改的东西。