对C#中的小代码示例进行基准测试,可以改进此实现吗?
经常如此,我发现自己在对小块代码进行基准测试,以确定哪种实现最快 我经常看到基准代码没有考虑jitting或垃圾收集器的注释 我有以下简单的基准测试功能,我已经慢慢发展:对C#中的小代码示例进行基准测试,可以改进此实现吗?,c#,.net,performance,profiling,C#,.net,Performance,Profiling,经常如此,我发现自己在对小块代码进行基准测试,以确定哪种实现最快 我经常看到基准代码没有考虑jitting或垃圾收集器的注释 我有以下简单的基准测试功能,我已经慢慢发展: static void Profile(string description, int iterations, Action func) { // warm up func(); // clean up GC.Collect(); var
static void Profile(string description, int iterations, Action func) {
// warm up
func();
// clean up
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++) {
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
这个实现有任何缺陷吗?在Z次迭代中,实现X比实现Y快,这足够好吗?你能想出什么办法来改进这一点吗
编辑
很明显,基于时间的方法(与迭代相反)是首选方法,有没有人有时间检查不影响性能的实现 如果您想排除GC交互,您可能希望在GC.对方付费呼叫之后而不是之前运行“预热”呼叫。这样,您就知道.NET已经从操作系统中为您的函数工作集分配了足够的内存 请记住,您正在为每个迭代调用一个非内联方法,因此请确保将您正在测试的内容与一个空体进行比较。您还必须接受,您只能可靠地计算比方法调用长几倍的时间
另外,根据你分析的内容,您可能希望在一定时间内,而不是在一定次数的迭代中,进行基于时间的运行—这样可以得到更容易比较的数字,而不必在很短的时间内实现最好的实现,也不必在很长的时间内实现最坏的实现。我完全不想通过委托:
public void Test()
{
int someNumber = 1;
Profiler.Profile("Closure access", 1000000,
() => someNumber + someNumber);
}
如果您不知道闭包,请在.NET Reflector中查看此方法。您还必须运行“预热”在实际测量之前通过,以排除JIT编译器在JIT代码上花费的时间。我认为像这样的基准测试方法最难克服的问题是考虑边缘情况和意外情况。例如,“这两个代码片段在高CPU负载/网络使用率/磁盘抖动等情况下如何工作?”它们非常适合进行基本逻辑检查,以查看某个特定算法是否比另一个算法工作得快得多。但是为了正确地测试大多数代码的性能,您必须创建一个测试来测量特定代码的特定瓶颈
我仍然认为,测试小块代码通常没有多少投资回报,并且会鼓励使用过于复杂的代码而不是简单的可维护代码。编写其他开发人员或我自己6个月后能够快速理解的清晰代码比高度优化的代码有更多的性能优势 在
GC.Collect
返回之前,不一定要完成定稿。最终结果将排队,然后在单独的线程上运行。此线程可能在测试期间仍处于活动状态,从而影响结果
如果要确保在开始测试之前完成最终确定,则可能需要调用,该调用将一直阻止,直到清除最终确定队列:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
这是修改后的功能:根据社区的建议,可以随意修改它,这是一个社区wiki
static double Profile(string description, int iterations, Action func) {
//Run at highest priority to minimize fluctuations caused by other processes/threads
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
// warm up
func();
var watch = new Stopwatch();
// clean up
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
watch.Start();
for (int i = 0; i < iterations; i++) {
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
return watch.Elapsed.TotalMilliseconds;
}
静态双配置文件(字符串描述、整数迭代、动作函数){
//以最高优先级运行,以最小化由其他进程/线程引起的波动
Process.GetCurrentProcess().PriorityClass=ProcessPriorityClass.High;
Thread.CurrentThread.Priority=ThreadPriority.Highest;
//热身
func();
var watch=新秒表();
//清理
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
watch.Start();
对于(int i=0;i
确保在启用优化的版本中编译,并在Visual Studio之外运行测试。最后一部分很重要,因为即使在发布模式下,JIT也会通过附加的调试器进行优化。我会多次调用
func()
进行预热,而不仅仅是一次。根据您进行基准测试的代码及其运行的平台,您可能需要考虑。要做到这一点,可能需要一个外部包装器来多次运行测试(在单独的应用程序域或进程中?),其中一些时候首先调用“填充代码”来强制对其进行JIT编译,以便使被基准测试的代码以不同的方式对齐。完整的测试结果将给出各种代码对齐的最佳情况和最坏情况的时间安排。改进建议
- 要检测是否附加了调试器,请阅读属性
(请记住还要处理调试器最初未附加,但经过一段时间后才附加的情况)System.Diagnostics.debugger.IsAttached
- 检测是否存在ji
static double Profile(string description, int iterations, Action func) { //Run at highest priority to minimize fluctuations caused by other processes/threads Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; Thread.CurrentThread.Priority = ThreadPriority.Highest; // warm up func(); var watch = new Stopwatch(); // clean up GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); watch.Start(); for (int i = 0; i < iterations; i++) { func(); } watch.Stop(); Console.Write(description); Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds); return watch.Elapsed.TotalMilliseconds; }
private bool IsJitOptimizerDisabled(Assembly assembly) { return assembly.GetCustomAttributes(typeof (DebuggableAttribute), false) .Select(customAttribute => (DebuggableAttribute) customAttribute) .Any(attribute => attribute.IsJITOptimizerDisabled); }
static void Profile(string description, int iterations, Action func) { // warm up func(); var watch = new Stopwatch(); // clean up GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); watch.Start(); for (int i = 0; i < iterations; i++) { func(); } watch.Stop(); Console.Write(description); Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds); }
static void ProfileGarbageMany(string description, int iterations, Action func) { // warm up func(); var watch = new Stopwatch(); // clean up GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); watch.Start(); for (int i = 0; i < iterations; i++) { func(); } GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); watch.Stop(); Console.Write(description); Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds); }
static void ProfileGarbage(string description, int iterations, Action func) { // warm up func(); var watch = new Stopwatch(); // clean up GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); watch.Start(); for (int i = 0; i < iterations; i++) { func(); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } watch.Stop(); Console.Write(description); Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds); }