很多时候,所以我觉得自己标杆码小块,看看哪些implemnetation是最快的。
很多时候,我看到了基准测试代码不考虑jitting或垃圾收集意见。
我有我已经慢慢演变下面这个简单的标杆功能:
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);
}
用法:
Profile("a descriptions", how_many_iterations_to_run, () =>
{
// ... code being profiled
});
这是否实施有任何瑕疵? 是不是不够好,表明implementaion X比实施Ÿ环Z迭代更快? 你能想到的,你会改善这种什么方法?
编辑它很清楚,一个基于时间的方法(而不是迭代),是首选,没有任何人有任何的实现在那里的时候检查不会影响性能?
Answer 1:
下面是修改的功能:所推荐的社区,可随时修改本它是一个社区的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;
}
确保在启用了优化版本编译和运行Visual Studio之外的测试 。 最后这部分很重要,因为JIT派驻过它的优化上附加一个调试,即使在发布模式。
Answer 2:
定稿不一定会前完成GC.Collect
回报。 定稿排队,然后在一个单独的线程运行。 此线程可能仍然活跃在你的测试,影响了成绩。
如果你想确保你开始测试,然后在此之前定稿已经完成了你可能要调用GC.WaitForPendingFinalizers
,这将阻止,直到终结队列被清除:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Answer 3:
If you want to take GC interactions out of the equation, you may want to run your 'warm up' call after the GC.Collect call, not before. That way you know .NET will already have enough memory allocated from the OS for the working set of your function.
Keep in mind that you're making a non-inlined method call for each iteration, so make sure you compare the things you're testing to an empty body. You'll also have to accept that you can only reliably time things that are several times longer than a method call.
Also, depending on what kind of stuff you're profiling, you may want to do your timing based running for a certain amount of time rather than for a certain number of iterations -- it can tend to lead to more easily-comparable numbers without having to have a very short run for the best implementation and/or a very long one for the worst.
Answer 4:
我会避免将委托可言:
- 代表呼叫是〜虚拟方法调用。 不便宜:在〜.NET最小的内存分配的25%。 如果你感兴趣的细节,请参见例如此链接 。
- 匿名委托可能导致倒闭的使用,你甚至不会注意到。 再次,访问闭合字段大于例如访问堆栈上的变量明显。
示例代码导致封用法:
public void Test()
{
int someNumber = 1;
Profiler.Profile("Closure access", 1000000,
() => someNumber + someNumber);
}
如果你不知道有关闭,看看在.net反射此方法。
Answer 5:
我认为最困难的问题与基准化这样的被占优势的情况下和意想不到的克服。 例如 - “你怎么到两个代码段高CPU负载/网络使用/磁盘颠簸的/ etc下工作。” 他们是伟大的基本逻辑检查,看是否有特定算法的工作比其他显著快。 但要正确地测试大多数代码的性能,你不得不创建一个测试测量的特定代码的特定瓶颈。
我还是说,测试的代码块往往对投资回报不大,并且可以鼓励使用过于复杂的代码,而不是简单的维护的代码。 字迹清晰的代码,其他的开发商,或在线下自己6个月,可以快速了解会比高度优化的代码更加的性能优势。
Answer 6:
Answer 7:
改进建议
检测是否执行环境是良好的基准(例如,如果调试器被连接检测或如果JIT优化被禁用,这将导致不正确的测量)。
测量的部分代码独立(,看看到底哪里瓶颈)。
- 不同的代码版本/组件/块比较(在你的第一句话你说“......标杆的代码小块,看看哪些是执行最快的。”)。
关于#1:
要检测一个附加调试器,读取属性System.Diagnostics.Debugger.IsAttached
(请记住,也处理在调试器最初未连接的情况下,但一段时间后附)。
为了检测是否JIT优化被禁用,读取属性DebuggableAttribute.IsJITOptimizerDisabled
相关组件:
private bool IsJitOptimizerDisabled(Assembly assembly) { return assembly.GetCustomAttributes(typeof (DebuggableAttribute), false) .Select(customAttribute => (DebuggableAttribute) customAttribute) .Any(attribute => attribute.IsJITOptimizerDisabled); }
关于#2:
这可以通过多种方式来完成。 一种方法是允许提供一些代表,然后逐一测量这些代表。
关于#3:
这也可以以多种方式完成,不同的使用情况将要求非常不同的解决方案。 如果基准手动调用,然后写入控制台可能被罚款。 但是,如果基准是由编译系统自动执行,然后写到控制台可能不是那么细。
要做到这一点的方法之一是返回基准测试结果为可以很容易地在不同的上下文中消耗一个强类型的对象。
Etimo.Benchmarks
另一种方法是使用现有的组件执行的基准。 其实,在我的公司,我们决定释放我们的基准测试工具,以公共领域。 在它的核心,它管理的垃圾收集器,抖动,热身等,就像一些其他的答案在这里建议。 它还具有三个特点我上面建议。 它管理几个讨论的问题埃里克利珀博客 。
这是一个输出例子,其中两个分量进行比较,并且将结果写入到控制台。 在这种情况下比较的两个部件被称为“KeyedCollection”和“MultiplyIndexedKeyedCollection”:
有一个NuGet包 ,一个样品NuGet包和源代码可在GitHub上 。 也有博客文章 。
如果你赶时间,我建议你的样品包装和简单地根据需要进行修改的样本代表。 如果你赶时间不,它可能是阅读博客文章,了解详细信息是个好主意。
Answer 8:
您还必须运行“热身”通过实际测量之前排除时间JIT编译器花费在jitting你的代码。
Answer 9:
根据您所基准的代码,并在其上运行的平台,你可能需要考虑代码比对如何影响系统性能 。 这样做可能会需要一个外部包装,多次测试运行(在单独的应用程序域或进程?),有的时候首先调用“填充码”,以迫使它被JIT编译,从而导致代码是基准进行不同的排列。 一个完整的测试结果将给予最好的和最坏情况下的时序为各种代码比对。
Answer 10:
如果你想消除基准垃圾收集影响齐全,是值得设置GCSettings.LatencyMode
?
如果没有,你想在创建垃圾的影响func
成为基准的一部分,那么你不应该也迫使收集在测试(定时器内)的结束?
Answer 11:
与你的问题最根本的问题是,一个单一的测量可以回答你所有的问题的假设。 你需要测量多次得到的情况,尤其是在像C#垃圾回收的langauge有效图像。
另一个答案给出了测量的基本性能的好办法。
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);
}
然而,这一次测量并没有考虑进行垃圾回收。 一个适当的轮廓还占了很多电话铺开垃圾回收的最坏情况下的性能(这个数字是有点没用,因为虚拟机可以终止而没有搜罗遗留下来的垃圾,但仍是比较两种不同的实现有用的func
。)
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);
}
但是,除了推荐任何具体可以额外的测量来分析更重要的是应该测量多个不同的统计数据,而不只是一种统计的想法。
文章来源: Benchmarking small code samples in C#, can this implementation be improved?