在软件开发过程中,性能优化是一个不可或缺的环节。为了衡量代码的性能,通常需要一个可靠的测试套件来比较不同实现方案的执行效率。本文将介绍一个简化性能比较管理的测试套件,并展示如何使用它来评估代码性能。
编写代码时,经常需要衡量代码的性能。大多数情况下,这很简单——只需编写测试函数,使用新旧代码计时,然后比较结果即可。然而,在更复杂的情况下,比如尝试优化多个相互依赖的函数时,一个函数的改进可能会导致另一个函数的性能下降。这时,就需要一种管理比较测试的方法。
使用这个测试套件非常简单。首先,定义一个类列表,这些类的性能想要进行比较(可以是同一个类的变体,也可以是完全不相关的类),然后编写多个测试函数。测试函数的签名如下:
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()" 方法来清除所有组合并重新开始。
下载包含以下五个文件: