为何C#中用Stopwatch测试时首个计时函数总是更快?
问题:C#迭代与递归性能测试中调用顺序影响耗时的原因?
我想要验证C#中迭代与递归哪种方式执行更快,但在简单测试中使用Stopwatch无法得到可信结果。
测试代码包含两个实现阶乘功能的函数,分别采用迭代和递归方式:
internal class Program { // 阶乘定义:从1到该数的所有整数乘积,0! = 1,负数无阶乘 static void Main(string[] args) { Console.WriteLine("Please Enter a Number:"); int number = Convert.ToInt32(Console.ReadLine()); double sw1Elapsed = 0d; double sw2Elapsed = 0d; double factorial = 0d; double factorial2 = 0d; Stopwatch sw = new Stopwatch(); sw.Reset(); for (int i = 0; i < 6; i++) { sw.Restart(); factorial = Factorial(number); sw.Stop(); sw1Elapsed += sw.Elapsed.TotalMilliseconds; sw.Restart(); factorial2 = FactorialByRecursion(number); sw.Stop(); sw2Elapsed += sw.Elapsed.TotalMilliseconds; Console.WriteLine("Iteration:"); Console.WriteLine($"Factorial of {number} = {factorial}"); Console.WriteLine($"Iteration Time taken = {sw1Elapsed}"); Console.WriteLine(); Console.WriteLine("Recursion:"); Console.WriteLine($"Factorial of {number} = {factorial2}"); Console.WriteLine($"Recursion Time taken = {sw2Elapsed}"); Console.WriteLine("-----------------------------------------------------"); } } public static double Factorial(int number) { if (number == 0) return 1; double factorial = 1; for (int i = number; i >= 1; i--) { factorial *= i; } return factorial; } public static double FactorialByRecursion(int number) { if (number == 0) return 1; return number * FactorialByRecursion(number - 1); } }
首次测试(迭代先调用)结果显示递归总耗时更低:
Please Enter a Number: 6 Iteration: Factorial of 6 = 720 Iteration Time taken = 0.3171 Recursion: Factorial of 6 = 720 Recursion Time taken = 0.0616 ----------------------------------------------------- Iteration: Factorial of 6 = 720 Iteration Time taken = 0.3172 Recursion: Factorial of 6 = 720 Recursion Time taken = 0.061700000000000005 ----------------------------------------------------- ...(后续循环耗时增量极小)
当调整调用顺序,先调用递归函数时,迭代总耗时反而更低:
Please Enter a Number: 6 Iteration: Factorial of 6 = 720 Iteration Time taken = 0.0571 Recursion: Factorial of 6 = 720 Recursion Time taken = 0.1066 ----------------------------------------------------- Iteration: Factorial of 6 = 720 Iteration Time taken = 0.0571 Recursion: Factorial of 6 = 720 Recursion Time taken = 0.1069 ----------------------------------------------------- ...(后续循环耗时增量极小)
我发现无论将哪个函数放在循环中第一个调用,后调用的函数总耗时总是更优,请问这一差异产生的原因是什么?(注:使用int类型存储阶乘结果时也会出现相同现象)
解答
核心原因:即时编译(JIT)与测试方法缺陷
你的测试结果异常,主要是两个因素共同作用:
JIT编译的首次调用开销
.NET程序运行时,方法不会提前编译为机器码,而是在第一次被调用时由CLR的JIT编译器将IL代码翻译为机器码,这个编译过程会产生额外耗时。- 第一个被调用的函数,其首次调用时间包含了JIT编译的完整开销;
- 第二个被调用的函数,虽然也是首次调用,但此时CLR的编译环境已经完成初始化,JIT编译的耗时会显著降低,导致首次调用时间远低于第一个函数。
测试方法的局限性
- 样本量太小:仅6次循环,首次调用的高开销会直接主导总耗时的统计结果,无法反映方法本身的执行性能;
- 连续调用的干扰:同一循环内连续调用两个函数,CPU缓存、JIT预热的交叉影响会进一步扭曲测试数据;
- 累加方式误导:你将每次调用的时间累加后展示,没有区分首次调用和后续调用的差异,导致总耗时的对比失去参考价值。
另外,从理论上来说,同逻辑的迭代性能本就优于递归——递归每次调用都需要创建栈帧、保存上下文,这些额外开销在大量调用时会被放大,但你的测试因为JIT的干扰,完全掩盖了这一点。
如何获得可信的性能对比结果
要准确测试两者的性能,需要优化测试流程:
- 预热消除JIT开销:正式计时前,先分别调用两个函数一次,触发JIT编译,避免首次调用的编译时间影响测试结果;
- 增大样本量:将循环次数提升到万级甚至十万级,让统计结果更稳定;
- 分开独立测试:不要在同一个循环内连续测试两个函数,分开循环测试,避免CPU缓存等因素的交叉干扰;
- 统计平均耗时:计算总耗时除以调用次数,得到单次调用的平均时间,而非累加总耗时。
优化后的示例代码:
static void Main(string[] args) { Console.WriteLine("Please Enter a Number:"); int number = Convert.ToInt32(Console.ReadLine()); // 预热:触发JIT编译,消除首次调用开销 Factorial(number); FactorialByRecursion(number); const int testCount = 100000; Stopwatch sw = new Stopwatch(); // 测试迭代性能 sw.Restart(); for (int i = 0; i < testCount; i++) { Factorial(number); } sw.Stop(); Console.WriteLine($"迭代单次平均耗时:{sw.Elapsed.TotalMilliseconds / testCount:F6} ms"); // 测试递归性能 sw.Restart(); for (int i = 0; i < testCount; i++) { FactorialByRecursion(number); } sw.Stop(); Console.WriteLine($"递归单次平均耗时:{sw.Elapsed.TotalMilliseconds / testCount:F6} ms"); }
使用这种方法测试后,就能明显看到迭代的性能优于递归,因为递归的栈帧开销会在大量调用中体现出来。
内容的提问来源于stack exchange,提问作者D.Man
相关产品推荐
相关产品推荐

