0.033秒的艺术 --- 测试程序性能
2008-11-28 00:51
190 查看
这是一系列关于C#与游戏编程性能提示的文章,先来看看如何简单测试一段C#程序的性能。
0.033秒的艺术 --- 测试程序性能
仅供个人学习使用,请勿转载,勿用于任何商业用途。
交互式的实时3D程序每秒至少渲染30帧图像,才能保证视觉上的平滑,这意味着所有渲染,AI,物理仿真都必须在0.033秒内完成。只有高度优化的代码和算法才能达到这样的要求。如何判断某个算法是否能满足要求,如何比较哪一种算法才是最优的呢?方法有很多,通过丢硬币,撒骰子或者凭感觉猜测等等,不过这些方法通常需要一点点运气的帮助。可惜我不是每天都能有那么好的运气,所以测试是我通常选择的方法。永远不要在测试结束之前作出任何结论。
几乎所有的测试都以时间和空间代价为主要衡量标准。然而考虑到目前2G内存已经降到了130米以下的价格,所以这里我只关心时间。但是也不要忘了程序和数据所占的空间越小,越能获得缓存所带来的好处,提高命中率。如何来测量一段程序的执行时间呢?啊,你可能马上想到了System.Times和System.Windows.Forms名称空间下的Timer或许还有DateTime。可惜这些计时器的精度都不太理想,通常几十毫秒才更新一次。我们需要高精度的时钟,这将用到2个win32 API:
QueryPerformanceCounter() 这个函数返回硬件支持的高精度计时器的值。
QueryPerformanceFrequency() 返回硬件计时器的频率。
通过直接访问硬件,这是一个精度非常高的时钟,可以把它的返回值想象为CPU的晶震数和晶振频率。在测试开始和结束的时候,分别查询一次计时器的值,最后相减除以计时器的频率,就能得到精确的时间间隔,以秒为单位,当然,可以进一步转换为任何你觉得方便的单位。可以说目前绝大部分3D游戏的计时系统都是以这2个函数为基础。为了在C#下使用他们,需要通过P/Invoke导入这这两个函数。好了,现在可以这样来测试一段程序的性能:
class Test
{
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceFrequency(out long lpFrequency);
static void Main(string[] args)
{
long startTime, endTime, freq;
QueryPerformanceFrequency(out freq);
QueryPerformanceCounter(out startTime);
//Do your test here
QueryPerformanceCounter(out endTime);
Console.WriteLine("frequency: {0:n0}", freq);
Console.WriteLine("time: {0:n5}s", (endTime - startTime)/(double)freq);
Console.Read();
}
}
也许是这样的代码太过普通和频繁,.net 2.0中,加入了对这个高精度计时器的支持。不再需要P/Invoke,只需记住System.Diagnostics.Stopwatch。现在可以把上面的代码简化为:
class TestClass
{
static void Main(String[] args)
{
Stopwatch timer = new Stopwatch();
timer.Start();
//Do your test here
timer.Stop();
Console.WriteLine(timer.ElapsedMilliseconds.ToString());
}
}
简单多了不是吗。但这段程序还不够好,测试代码之前的代码可能产生了很多垃圾,而当你开始测试时,垃圾回收发生了,显然,这是不是我们所希望的。虽然我们不能控制GC精确的执行时间,但可以把GC对测试代码的影响减小到最低程度:
class TestClass
{
static void Main(String[] args)
{
Stopwatch timer = new Stopwatch();
// initialize your code here
GC.Collect(2);
GC.WaitForPendingFinalizers();
GC.Collect(2);
GC.WaitForPendingFinalizers();
timer.Start();
//Do your test here
timer.Stop();
Console.WriteLine(timer.ElapsedMilliseconds.ToString());
}
}
在测试之前强制垃圾回收。WaitForPendingFinalizers()将会挂起当前线程,直到GC完全结束。注意,这里强制进行了2次垃圾回收。如果你对垃圾回收有所了解的话,那么应该知道当GC发现一个对象有Finalize()方法时,会先调用Finalize(),然后到下一次GC发生时才回收这个对象,我们确保在测试之前所有垃圾都已经回收释放了。
好了,现在我们有了一个简单实用的测试模板,不过还有几点忠告是在测试时必须记住的 :
1. 在测试结束之前永远不要下结论
2. 在测试之前,至少执行一次测试代码。比如你的测试代码是Test(),那么在timer.start()前至少执行一次Test(),排除JIT对测试结果的影响。
3. 多运行几次程序,取平均值作为测试结果
4. 不要在Debug模式下执行测试
5. 不要在测试代码中输出测试信息,Console是相对较慢的操作。
6. 不同的CLR和编译器版本下的测试结果可能会不同。
以上忠告都来自好心的Tobias Hertkorm
现在你已经成为性能分析大师了。不要再到论坛上问究竟是for还是foreach快这样的问题了,自己动手试试吧。当然,如果你比我还懒,那么下次来看我对for和foreach的分析结果吧 :)
0.033秒的艺术 --- 测试程序性能
仅供个人学习使用,请勿转载,勿用于任何商业用途。
交互式的实时3D程序每秒至少渲染30帧图像,才能保证视觉上的平滑,这意味着所有渲染,AI,物理仿真都必须在0.033秒内完成。只有高度优化的代码和算法才能达到这样的要求。如何判断某个算法是否能满足要求,如何比较哪一种算法才是最优的呢?方法有很多,通过丢硬币,撒骰子或者凭感觉猜测等等,不过这些方法通常需要一点点运气的帮助。可惜我不是每天都能有那么好的运气,所以测试是我通常选择的方法。永远不要在测试结束之前作出任何结论。
几乎所有的测试都以时间和空间代价为主要衡量标准。然而考虑到目前2G内存已经降到了130米以下的价格,所以这里我只关心时间。但是也不要忘了程序和数据所占的空间越小,越能获得缓存所带来的好处,提高命中率。如何来测量一段程序的执行时间呢?啊,你可能马上想到了System.Times和System.Windows.Forms名称空间下的Timer或许还有DateTime。可惜这些计时器的精度都不太理想,通常几十毫秒才更新一次。我们需要高精度的时钟,这将用到2个win32 API:
QueryPerformanceCounter() 这个函数返回硬件支持的高精度计时器的值。
QueryPerformanceFrequency() 返回硬件计时器的频率。
通过直接访问硬件,这是一个精度非常高的时钟,可以把它的返回值想象为CPU的晶震数和晶振频率。在测试开始和结束的时候,分别查询一次计时器的值,最后相减除以计时器的频率,就能得到精确的时间间隔,以秒为单位,当然,可以进一步转换为任何你觉得方便的单位。可以说目前绝大部分3D游戏的计时系统都是以这2个函数为基础。为了在C#下使用他们,需要通过P/Invoke导入这这两个函数。好了,现在可以这样来测试一段程序的性能:
class Test
{
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceFrequency(out long lpFrequency);
static void Main(string[] args)
{
long startTime, endTime, freq;
QueryPerformanceFrequency(out freq);
QueryPerformanceCounter(out startTime);
//Do your test here
QueryPerformanceCounter(out endTime);
Console.WriteLine("frequency: {0:n0}", freq);
Console.WriteLine("time: {0:n5}s", (endTime - startTime)/(double)freq);
Console.Read();
}
}
也许是这样的代码太过普通和频繁,.net 2.0中,加入了对这个高精度计时器的支持。不再需要P/Invoke,只需记住System.Diagnostics.Stopwatch。现在可以把上面的代码简化为:
class TestClass
{
static void Main(String[] args)
{
Stopwatch timer = new Stopwatch();
timer.Start();
//Do your test here
timer.Stop();
Console.WriteLine(timer.ElapsedMilliseconds.ToString());
}
}
简单多了不是吗。但这段程序还不够好,测试代码之前的代码可能产生了很多垃圾,而当你开始测试时,垃圾回收发生了,显然,这是不是我们所希望的。虽然我们不能控制GC精确的执行时间,但可以把GC对测试代码的影响减小到最低程度:
class TestClass
{
static void Main(String[] args)
{
Stopwatch timer = new Stopwatch();
// initialize your code here
GC.Collect(2);
GC.WaitForPendingFinalizers();
GC.Collect(2);
GC.WaitForPendingFinalizers();
timer.Start();
//Do your test here
timer.Stop();
Console.WriteLine(timer.ElapsedMilliseconds.ToString());
}
}
在测试之前强制垃圾回收。WaitForPendingFinalizers()将会挂起当前线程,直到GC完全结束。注意,这里强制进行了2次垃圾回收。如果你对垃圾回收有所了解的话,那么应该知道当GC发现一个对象有Finalize()方法时,会先调用Finalize(),然后到下一次GC发生时才回收这个对象,我们确保在测试之前所有垃圾都已经回收释放了。
好了,现在我们有了一个简单实用的测试模板,不过还有几点忠告是在测试时必须记住的 :
1. 在测试结束之前永远不要下结论
2. 在测试之前,至少执行一次测试代码。比如你的测试代码是Test(),那么在timer.start()前至少执行一次Test(),排除JIT对测试结果的影响。
3. 多运行几次程序,取平均值作为测试结果
4. 不要在Debug模式下执行测试
5. 不要在测试代码中输出测试信息,Console是相对较慢的操作。
6. 不同的CLR和编译器版本下的测试结果可能会不同。
以上忠告都来自好心的Tobias Hertkorm
现在你已经成为性能分析大师了。不要再到论坛上问究竟是for还是foreach快这样的问题了,自己动手试试吧。当然,如果你比我还懒,那么下次来看我对for和foreach的分析结果吧 :)
相关文章推荐
- 0.033秒的艺术 --- 测试程序性能
- 用BenchmarkDotNet给C#程序做性能测试
- ASP.NET权限组件,生成10万条测试数据检测程序的大数据性能改进
- opcache提升php程序性能测试
- Linux C 程序性能测试 valgrind callgrind分析函数耗时、perf分析函数CPU消耗
- ASP.NET权限组件,生成10万条测试数据检测程序的大数据性能改进
- 创建小型测试程序排除软件故障的艺术
- GCC高级测试功能扩展——程序性能测试工具gprof、程序覆盖测试工具gcov
- 应用软件性能测试的艺术(翻译)——序
- Go语言HTTP测试及程序性能调优
- Linux 性能测试程序
- C#代码性能测试程序编写要点
- 性能测试培训:通用互联网服务端程序
- LoadRunner调用Java程序—性能测试
- Linux下用JMap对Java程序进行性能测试检查内存泄露问题
- 也谈程序性能测试
- 之前写的SSL的性能测试程序
- 【性能测试】验证程序运行时所占用的时间Cycle
- 用Stopwatch测试程序执行性能时间
- GCC高级测试功能扩展——程序性能测试工具gprof、程序覆盖测试工具gcov