简体   繁体   English

如何在C#中对单元测试性能优化进行单元测试?

[英]How can I unit test performance optimisations in C#?

I'm using an optimised version of Levenshtein's algorithm in some search code I'm building. 我在我正在构建的一些搜索代码中使用了Levenshtein算法的优化版本。 I have functional unit tests to verify that the algorithm is returning the correct results, but in this context the performance of the algorithm is also hugely important. 我有功能单元测试来验证算法返回正确的结果,但在这种情况下,算法的性能也非常重要。

I'm looking to add some test coverage to the project so that if any future modifications affect the optimisations, they'll show up as failing tests - because the algorithm is deterministic and running against known test data, this could be as detailed as counting the number of instructions executed for a given set of test inputs. 我希望为项目添加一些测试覆盖率,以便如果将来的任何修改影响优化,它们将显示为失败测试 - 因为算法是确定性的并且针对已知测试数据运行,这可能与计数一样详细为给定的一组测试输入执行的指令数。 In other words, I'm not looking to measure algorithm performance using timers - I'm interested in actually testing the algorithm's internal behaviour instead of just the output. 换句话说,我不打算使用定时器测量算法性能 - 我对实际测试算法的内部行为而不仅仅是输出感兴趣。

Any ideas how I would approach this in C#/.NET 4? 有什么想法我会如何在C#/ .NET 4中解决这个问题?

EDIT: The reason I don't want to just use wall-clock time is that it'll vary with CPU load and other factors outside the control of the test. 编辑:我不想只使用挂钟时间的原因是它会随着CPU负载和测试控制之外的其他因素而变化。 That could lead to tests that fail when the build server is under load, for example. 例如,这可能导致在构建服务器负载时测试失败。 There will be wall-clock monitoring as part of the deployed system. 作为部署系统的一部分, 有挂钟监控。

EDIT 2: Think of it this way... how would you apply red->green->refactor when performance is a critical requirement? 编辑2:这样想吧......当性能是关键要求时,你会如何应用red-> green-> refactor?

I'm going to answer the third part of your question, since I've done this with some success several times. 我将回答你问题的第三部分,因为我已经多次取得了一些成功。

how would you apply red->green->refactor when performance is a critical requirement? 当性能是关键要求时,你会如何应用red-> green-> refactor?

  1. Write pinning tests to catch regressions, for what you plan to change and other methods that may slow down as a result of your changes. 编写固定测试以捕获回归,计划更改的内容以及可能因更改而变慢的其他方法。
  2. Write a performance test that fails. 编写失败的性能测试。
  3. Make performance improvements, running all tests frequently. 提高性能,经常运行所有测试。
  4. Update your pinning tests to more closely pin the performance. 更新固定测试以更紧密地确定性能。

Write pinning tests 写钉扎测试

Create a helper method like this to time what you want to pin. 创建一个这样的辅助方法来计算你想要的东西。

private TimeSpan Time(Action toTime)
{
    var timer = Stopwatch.StartNew();
    toTime();
    timer.Stop();
    return timer.Elapsed;
}

Then write a test that asserts your method takes no time: 然后编写一个测试,断言你的方法不花时间:

[Test]
public void FooPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0));
}

When it fails (with the actual time elapsed in the failure message), update the time with something slightly more than the actual time. 当它失败时(失败消息中已经过了实际时间),用稍微超过实际时间的东西更新时间。 Rerun and it will pass. 重新运行,它将通过。 Repeat this for other functions whose performance you might impact with your changes, ending up with something like this. 对可能影响您的更改的性能的其他函数重复此操作,最后得到类似的结果。

[Test]
public void FooPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0.8));
}
[Test]
public void BarPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Bar()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(6));
}

Write a failing performance test 写一个失败的性能测试

I like to call this kind of test a "baiting test". 我喜欢称这种测试为“诱饵测试”。 It's just the first step of a pinning test. 这只是钉扎测试的第一步。

[Test]
public void FooPerformance_Bait()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0));
}

Now, work on performance improvements. 现在,开展性能改进。 Run all the tests (pinning and baiting) after each tentative improvement. 在每次试验性改进之后运行所有测试(钉扎和诱饵)。 If you are successful, you'll see the time going down in the failure output of the baiting test, and none of your pinning tests will fail. 如果您成功,您将看到诱饵测试的失败输出中的时间下降,并且您的任何钉扎测试都不会失败。

When you are satisfied with the improvements, update the pinning test for the code you changed, and delete the baiting test. 如果您对这些改进感到满意,请更新您更改的代码的固定测试,并删除诱饵测试。

What do you do with these tests now? 你现在对这些测试做了什么?

The least worrisome thing to do is to mark these tests with the Explicit attribute, and keep them around for the next time you want to check performance. 最不令人担忧的事情是使用Explicit属性标记这些测试,并在下次要检查性能时保留它们。

On the opposite side of the work spectrum, creating a reasonably well controlled subsystem in CI for running these kind of tests is a really good way to monitor performance regressions. 在工作范围的另一端,在CI中创建一个控制合理的子系统来运行这些测试是监控性能回归的一种非常好的方法。 In my experience there is a lot of worry about them "failing randomly due to CPU load from something else" than there are actual failures. 根据我的经验,有很多人担心它们“由于来自其他方面的CPU负载而随机失败”而不是实际失败。 The success of this kind of effort depends more on team culture than your ability to exercise control over the environment. 这种努力的成功更多地取决于团队文化,而不是你对环境进行控制的能力。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM