咨询区
Ricky G:
我正在给项目做性能和代码优化,比如将重复的代码提炼成到一个可重用的方法中,为了能够达到可重用目的,我用 Func<T>
作为方法参数。
public int Calculate(Func<int> expr){return expr();}
当我用 Benchmark 对优化前和优化后的代码做基准测试时,居然发现优化后的在性能上慢了两倍?
完整的代码如下:
using System;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;namespace ConsoleApp1
{class Program{static void Main(string[] args){var summary = BenchmarkRunner.Run<Calculations>();var logger = ConsoleLogger.Default;MarkdownExporter.Console.ExportToLog(summary, logger);Console.WriteLine(summary);}}public class Calculations{public Random RandomGeneration = new Random();[Benchmark]public void CalculateNormal(){var s = RandomGeneration.Next() * RandomGeneration.Next();}[Benchmark]public void CalculateUsingFunc(){Calculate(() => RandomGeneration.Next() * RandomGeneration.Next());}[MethodImpl(MethodImplOptions.AggressiveInlining)]public int Calculate(Func<int> expr){return expr();}}
}
回答区
Jon Skeet:
慢两倍的真正原因在于你的每一次调用都会生成一个新的 delegate,所以我一点都不感到惊讶。
如果你的 lambda 表达式不需要捕获 this 或者其他 local 变量,那你完全可以将其提取成一个单例形式,这样就可以避免每一次都调用new了,开销自然就大大减少了。
下面是我修改后的代码:
public class Calculations
{public Random RandomGeneration = new Random();private Func<int> exprField;public Calculations(){exprField = () => RandomGeneration.Next() * RandomGeneration.Next();}[Benchmark]public void CalculateNormal(){var s = RandomGeneration.Next() * RandomGeneration.Next();}[Benchmark]public void CalculateUsingFunc(){Calculate(() => RandomGeneration.Next() * RandomGeneration.Next());}[Benchmark]public void CalculateUsingFuncField(){Calculate(exprField);}[MethodImpl(MethodImplOptions.AggressiveInlining)]public int Calculate(Func<int> expr){return expr();}
}
benchmark 结果。
| Method | Mean | Error | StdDev |
|------------------------ |---------:|---------:|---------:|
| CalculateNormal | 27.61 ns | 0.438 ns | 0.388 ns |
| CalculateUsingFunc | 48.74 ns | 1.009 ns | 0.894 ns |
| CalculateUsingFuncField | 32.53 ns | 0.698 ns | 0.717 ns |
虽然还是有一些差距,但相比你的方式已经大大改善了。
点评区
这个确实是在写代码的过程中容易忽视的地方,学习了,用 Benchmark 做基准测试也是值得大家了解的。