性能比较测试套件

在软件开发过程中,性能优化是一个不可或缺的环节。为了衡量代码的性能,通常需要一个可靠的测试套件来比较不同实现方案的执行效率。本文将介绍一个简化性能比较管理的测试套件,并展示如何使用它来评估代码性能。

编写代码时,经常需要衡量代码的性能。大多数情况下,这很简单——只需编写测试函数,使用新旧代码计时,然后比较结果即可。然而,在更复杂的情况下,比如尝试优化多个相互依赖的函数时,一个函数的改进可能会导致另一个函数的性能下降。这时,就需要一种管理比较测试的方法。

使用测试套件

使用这个测试套件非常简单。首先,定义一个类列表,这些类的性能想要进行比较(可以是同一个类的变体,也可以是完全不相关的类),然后编写多个测试函数。测试函数的签名如下:

struct my_test_name { template void execute() { // 测试代码,使用类型 "value_type" } };

由于函数需要被包装在结构体中,所以有一个宏来简化声明,可以这样声明测试函数:

COMPARATIVE_TEST(my_function_name) { // 测试代码,使用类型 "value_type" }

然后,将它们组合在类型列表中,并传递给测试套件类。例如,如果想检查乘以3和连续加3次哪个更快,以及不同类型(int、float、double或_int64)的结果如何比较,甚至除法如何表现(虽然这个例子是虚构的,但只是为了说明语法和类用法),可以声明三个测试函数:

COMPARATIVE_TEST(multiply) { volatile value_type var; for (size_t i = 0; i < 10000000; ++i) { var = ((value_type)i) * ((value_type)3); } } COMPARATIVE_TEST(multiply_via_add) { volatile value_type var; for (size_t i = 0; i < 10000000; ++i) { var = (value_type)i + (value_type)i + (value_type)i; } } COMPARATIVE_TEST(divide) { volatile value_type var; for (size_t i = 1; i < 10000000; ++i) { var = (value_type)100000000 / (value_type)i; } }

(添加了 "volatile" 以阻止编译器优化代码)。现在,声明要检查的类型的类型列表:

typedef SimpleTypeList::Append::Result::Append::Result::Append<__int64>::Result TestTypes;

或者,可以使用宏代替:

typedef TYPELIST_4(int, float, double, __int64) TestTypes;

然后,声明测试函数的类型列表:

typedef SimpleTypeList::Append::Result::Append::Result TestFuncs;

或者再次使用宏:

typedef TYPELIST_3(multiply, multiply_via_add, divide) TestFuncs;

最后,使用测试套件:

CSimpleComparativeTest test; test.registerTests();

当调用 "registerTests" 时,它会生成所有提供的类型与所有提供的函数的组合。在这个例子中,它将创建12个 "类型-函数调用" 组合。如果有一些函数应该只在类型子集上执行,定义另一个类型列表,并再次调用 registerTests,新的组合将被添加到现有的组合中。例如,只想检查浮点数乘法对浮点数类型。首先,定义测试函数:

COMPARATIVE_TEST(multiply_float) { volatile value_type var; for (size_t i = 1; i < 10000000; ++i) { var = ((value_type)i) * ((value_type)1.234f); } }

现在定义类型列表,并注册这个组合作为之前的补充:

typedef TYPELIST_2(float, double) TestFloatTypes; typedef TYPELIST_1(multiply_float) TestFloatFuncs; test.registerTests();

现在准备执行测试:

test.test(10);

在这个调用中,每个 "类型-函数调用" 组合将被执行10次("test" 的参数),然后结果将被平均。现在,可以获取输出表格:

std::string result = test.getResult();

输出表格看起来像这样:

也可以以转置视图获取它:

std::string result = test.getResult(true);

在那些没有执行组合的单元格中,可以看到 "N/A"。单元格中的数字代表每个组合执行所需的平均时间(以tick为单位,每个tick大约是1ms)。当然,这是墙钟时间,在同一台机器上,在不同的时间段内,数字会有所不同,但测试套件的整个目的是进行比较测量,而不是绝对测量。一些类有很长的系统名称,尤其是模板——它们的名称被完全展开,这使得它们几乎无法阅读。如果出现这种情况,在注册了这些类之后,可以为输出设置 "nice" 名称,例如:

test.setTypeName("std::string");

函数名称也是如此:

test.setFunctionName("nice_name");

此外,可以调用 "clear()" 方法来清除所有组合并重新开始。

下载包含以下五个文件:

  • SimpleTest.h - 实际的 "suit" 实现
  • SimpleTypeLists.h - 辅助文件(简单、直接的类型列表实现)
  • SimpleAnyValue.h - 辅助文件("any" 的实现,与 boost::any 类似,但有所扩展)
  • Test.cpp - 示例文件
  • Test.vcxproj - 示例项目(VC10)
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485