性能比较测试套件





5.00/5 (3投票s)
用于轻松比较代码变体性能的代码。
引言
我看到的所有测试框架都针对正确性检查,并且没有一个提供性能比较检查。 也许我重新发明了轮子,但这里有一个小的测试“套件”,简化了性能比较的管理。
背景
当您编写一些代码时,有时您需要一种方法来衡量它的比较性能。 大部分时间它非常简单 - 只需要编写测试函数,用新代码和旧代码计时,然后完成。 但有时您会面临更复杂的情况。 例如,您尝试了几个相互依赖的函数的变体。 一个函数的改进可能导致另一个函数的退化。 这可能值得(例如,函数 1 变得快了 100%,但函数 2 变慢了 10%,并且它们的使用大致相等),也可能不值得(例如,函数 1 变得快了 1000%,函数变慢了 10%,但函数 1 一年只使用一次,而函数 2 每秒都使用)。 也就是说,有时您会发现自己需要对比较测试进行一些管理。
使用代码
用法非常简单。 您定义要比较其性能的类列表(它可以是相同类的变体,也可以是完全不相关的类),并编写许多测试函数。 测试函数签名很简单
struct my_test_name
{
template<typename value_type>
void execute()
{
// your code to test on type "value_type"
}
}
因为函数需要封装到结构中,所以有一个宏来简化声明,因此您可以这样声明测试函数
COMPARATIVE_TEST(my_function_name)
{
// your code to test on type "value_type"
}
然后您只需将它们组合到类型列表中,并将其提供给测试套件类。 这是一个例子。 假设您想检查哪个更快:乘以 3,还是将相同的数字加 3 次? 不同类型(int、float、double 或 _int64)的结果比较如何? 除法呢? (我理解这个例子是虚假的,但它只是为了说明语法和类用法)。 所以我们声明了三个测试函数
COMPARATIVE_TEST(mutiply)
{
volatile value_type var;
for (size_t i = 0; i<10000000; ++i)
var=((value_type)i)*((value_type)3);
}
COMPARATIVE_TEST(mutiply_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<int>::
Append<float>::Result::
Append<double>::Result::
Append<__int64>::Result TestTypes;
或者,您可以使用宏代替
typedef TYPELIST_4(int, float, double, __int64) TestTypes;
然后,声明测试函数的类型列表
typedef SimpleTypeList<mutiply>::
Append<mutiply_via_add>::Result::
Append<divide>::Result TestFuncs;
或者,再次使用宏
typedef TYPELIST_3(mutiply, mutiply_via_add, divide) TestFuncs;
最后,使用测试套件
CSimpleComparativeTest test;
test.registerTests<TestTypes, TestFuncs>();
当您调用“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<TestFloatTypes, TestFloatFuncs>();
现在我们准备好执行测试了
test.test(10);
在此调用期间,每个“类型-函数调用”组合将以随机顺序执行 10 次(“test”的参数),然后将对结果进行平均。 现在,您可以获得一个输出表
std::string result = test.getResult();
输出表看起来像这样
您也可以将其以转置视图获取
std::string result = test.getResult(true);
您在那些没有要执行的组合的单元格中看到“N/A”。 单元格中的数字表示每个组合执行的平均时间(以刻度为单位,每个刻度约为 1 毫秒)。 当然,这是墙上时间,在同一台机器上,在不同的时期,数字会有些不同,但整个套件的目的是比较测量,而不是绝对测量。 某些类具有非常长的系统名称,尤其是模板 - 它们的名称会完全展开,这使得它们几乎不可读。 如果是这种情况,在您注册了这些类之后,您可以为输出设置“漂亮”的名称,例如
test.setTypeName<std::string>("std::string");
函数名称也是如此
test.setFunctionName<myverylongunreadablefunctionname>("nice_name");
此外,您可以调用“clear()
”方法来清除所有组合并重新开始。
下载包含五个文件
- SimpleTest.h - 实际的“套件”实现
- SimpleTypeLists.h - 帮助程序文件(简单、直接的类型列表实现)
- SimpleAnyValue.h - 帮助程序文件(“any”的实现,类似于
boost::any
,但扩展) - Test.cpp - 示例文件
- Test.vcxproj - 示例项目 (VC10)
就是这样,祝您编码愉快!
更新于 2013 年 8 月 19 日
我有了第二次机会来查看代码,所以我清理了接口和代码; 现在单个类型/函数不需要封装到TYPELIST_1()
中 - 您可以像这样注册类型/函数,即“registerTests<int, funcname>()”。