转:C++ 性能测试支持
转: http://codinginet.com/articles/view/201606-use_gtestx_for_benchmark?simple=1&from=timeline&isappinstalled=0
使用gtestx写C++性能测试
By mikewei at 2016-06-05 01:00 阅读(233)
在互联网后台开发领域,针对性能的单元测试常被忽视,实际上它非常重要,至少与普通的unit-test一样重要。特别是对一些基础库、关键部分代码,必要的性能单元测试,可帮助在模块粒度上发现并解决性能问题,这比在系统粒度上分析解决性能问题要容易地多。更为重要的,关注并熟悉模块级性能,是架构师掌控系统整体性能的基础,这在架构设计、线上问题分析等场景中会经常被使用。在过去几年工作中我写过很多的性能单测代码,它们帮助我更好地理解各种系统调用、数据结构、组件的性能;在我经历的几个海量系统研发过程中,严格的性能单测也对项目最终质量起到了显著的作用。
我在写性能测试方面(主要是C/C++)还算是有过不少经验,写这种代码虽不难,但是比较重复和枯燥,后来我尝试写了一些框架工具来简化这类工作,gtestx就是其中之一,开始只是个人工具,最近一年多在项目团队中推广使用大家觉得还不错。今天就系统性地介绍下这个工具。
gtestx是基于gtest的一个扩展,从取名上也很容易理解。为什么基于gtest呢?因为首先gtest基本上是现在C++的单元测试的主流选择,并且有广泛的群众基础,基于gtest来做测试测试可以更容易上手,也就是说,如果你已经在用gtest写单元测试了,基本不需要学习和额外工作就可以直接写gtestx的tests;另外gtest已经实现了一套比较先进好用的tests管理机制,性能单测本质上说也是一种单元测试,所以gtest很多优秀的特性gtestx可以直接继承复用。
下面我们就从几个实例出发,来介绍一下gtestx的用法。
简单用例
首先一个简单的例子,测试系统调用getppid的性能:
#include <sys/types.h>
#include <unistd.h>
#include "gtestx/gtestx.h" // Just like TEST macro, use PERF_TEST for performance testing
PERF_TEST(SimpleTest, SysCallPerf)
{
getppid();
}
可以看出它跟gtest的TEST宏用法很像:
TEST(SimpleTest, SysCall)
{
ASSERT_NE(0, getppid());
}
实际上PERF_TEST跟TEST确实非常类似,它的两个参数的含意与TEST相同,都是表示gtest框架中的TestCaseName和TestName。所不同的是,PERF_TEST的函数体并非只像TESt的函数体一样只运行一次,而是(默认)会死循环一段特定的时间并统计相关counter。在用例执行时,PERF_TEST也会自动将统计的性能数据打印出来:
$ ./gtestx_examples --gtest_filter='SimpleTest.*'
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from SimpleTest
[ RUN ] SimpleTest.SysCall
[ OK ] SimpleTest.SysCall (0 ms)
[ RUN ] SimpleTest.SysCallPerf
count: 25,369,636
time: 1.508939 s
HZ: 16,812,897.009090
1/HZ: 0.000000059 s
[ OK ] SimpleTest.SysCallPerf (1509 ms)
[----------] 2 tests from SimpleTest (1509 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (1510 ms total)
[ PASSED ] 2 tests.
从上面可以看出SimpleTest.SysCallPerf这个性能测试Test一共循环执行了25369636次,总共1.5秒时长,调用性能为每秒1681万次,平均每次调用耗时59纳秒。
Fixture用例
很多时候我们性能测试的过程是,先初始化需要的状态,然后性能测试,最后清理资源。这时我们适合使用gtest中的Fixture用例的gtestx版本。我们举一个具体一点的例子,比如要测试std::map的查找性能,需要先在map中插入一定数量的元素,然后执行性能单测,可以实现为下面的代码:
#include <map>
#include "gtestx/gtestx.h" class StdMapTest : public testing::Test
{
protected:
virtual ~StdMapTest() {}
virtual void SetUp() override {
for (int i = 0; i < 1000; i++) {
map_.emplace(i, 1);
}
}
virtual void TearDown() override {} std::map<int,int> map_;
}; PERF_TEST_F(StdMapTest, FindPerf)
{
map_.find(999);
}
如上代码中,初始化部分在SetUp函数中完成,PERF_TEST_F宏是原有TEST_F宏的一个gtestx版本,它只需一行代码,非常简单。用例执行结果如下:
$ ./gtestx_examples --gtest_filter='StdMapTest.FindPerf'
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from StdMapTest
[ RUN ] StdMapTest.FindPerf
count: 3,187,592
time: 1.508904 s
HZ: 2,112,521.406266
1/HZ: 0.000000473 s
[ OK ] StdMapTest.FindPerf (1512 ms)
[----------] 1 test from StdMapTest (1512 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1512 ms total)
[ PASSED ] 1 test.
带参数的Fixture用例
假设我们要测试std::unordered_map的查找性能,但hash表的性能跟它的桶数量有很大的关系,我们希望测试在不同节点数/桶数量的配置下的查找性能的情况。若用普通方法我们需要根据不同的配置写多个用例,但使用带参数的Fixture用例一个就可以搞定了:
#include <unordered_map>
#include "gtestx/gtestx.h" class HashMapTest : public testing::TestWithParam<float>
{
protected:
virtual ~HashMapTest() {}
virtual void SetUp() override {
map_.max_load_factor(GetParam());
for (int i = 0; i < 1000; i++) {
map_.emplace(i, 1);
}
}
virtual void TearDown() override {} std::unordered_map<int,int> map_;
int search_key_ = 0;
}; INSTANTIATE_TEST_CASE_P(LoadFactor, HashMapTest,
testing::Values(3.0, 1.0, 0.3)); PERF_TEST_P(HashMapTest, FindPerf)
{
map_.find(++search_key_);
}
上面代码中我们使用INSTANTIATE_TEST_CASE_P宏设定需要测试的不同参数值(这点与gtest中是完全相同),然后使用PERF_TEST_P定义性能测试的代码(跟gtest中的TEST_P宏对应)。它的执行结果如下,可以看出性能随着节点/桶数的比例参数下降而升高:
$ ./gtestx_examples --gtest_filter='*HashMapTest.FindPerf*'
[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from LoadFactor/HashMapTest
[ RUN ] LoadFactor/HashMapTest.FindPerf/0
count: 4,629,181
time: 1.509068 s
HZ: 3,067,576.146337
1/HZ: 0.000000326 s
[ OK ] LoadFactor/HashMapTest.FindPerf/0 (1511 ms)
[ RUN ] LoadFactor/HashMapTest.FindPerf/1
count: 11,975,484
time: 1.508859 s
HZ: 7,936,781.369233
1/HZ: 0.000000126 s
[ OK ] LoadFactor/HashMapTest.FindPerf/1 (1510 ms)
[ RUN ] LoadFactor/HashMapTest.FindPerf/2
count: 16,320,267
time: 1.508913 s
HZ: 10,815,909.863591
1/HZ: 0.000000092 s
[ OK ] LoadFactor/HashMapTest.FindPerf/2 (1509 ms)
[----------] 3 tests from LoadFactor/HashMapTest (4530 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (4530 ms total)
[ PASSED ] 3 tests.
模板Fixture用例
又假设我们要测试std::queue的进队出队的性能,并且需要考虑不同的元素类型(std::queue的模板参数类型)下的性能情况,我们可以使用基于模板Fixture的用例:
#include <queue>
#include "gtestx/gtestx.h" template <class T>
class QueueTest : public testing::Test
{
protected:
virtual ~QueueTest() {}
virtual void SetUp() override {}
virtual void TearDown() override {} std::queue<T> queue_;
}; typedef testing::Types<int, std::vector<char>> TestTypes;
TYPED_TEST_CASE(QueueTest, TestTypes); TYPED_PERF_TEST(QueueTest, PushPopPerf)
{
this->queue_.push(TypeParam());
this->queue_.pop();
}
如上面代码,我们使用TYPED_TEST_CASE定义了int和std::vector<char>两个类参数分别来做性能测试,这时基于模板的用例使用TYPED_PERF_TEST定义(与gtest中的TYPED_TEST对应)。执行结果如下:
$ ./gtestx_examples --gtest_filter='QueueTest*PushPopPerf*'
[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from QueueTest/0, where TypeParam = int
[ RUN ] QueueTest/0.PushPopPerf
count: 37,617,883
time: 1.509303 s
HZ: 24,924,009.956914
1/HZ: 0.000000040 s
[ OK ] QueueTest/0.PushPopPerf (1509 ms)
[----------] 1 test from QueueTest/0 (1509 ms total)
[----------] 1 test from QueueTest/1, where TypeParam = std::vector<char, std::allocator<char> >
[ RUN ] QueueTest/1.PushPopPerf
count: 9,433,683
time: 1.508871 s
HZ: 6,252,146.803802
1/HZ: 0.000000160 s
[ OK ] QueueTest/1.PushPopPerf (1509 ms)
[----------] 1 test from QueueTest/1 (1509 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (3018 ms total)
[ PASSED ] 2 tests.
PERF_ABORT
如果你想在PERF函数体中使用ASSERT_XXX等gtest宏,需要注意当断言fail时默认它只能跳出当前函数还不是整个性能测试的用例,所以gtestx中增加了一个辅助宏来帮助能正确地跳出整个用例。所以当使用ASSERT_XXX等宏时,应当使用类似下面的代码:
PERF_TEST(MiscFeaturesTest, PerfAbort)
{
ASSERT_NE(0, getppid()) << PERF_ABORT;
}
指定频率和时长
默认gtestx对每个PERF用例的执行频率总是死循环压满,执行时长默认是固定的(1.5秒)。这两个参数实际都可以修改。什么场景需要修改呢?比如我们希望观察一定调用频率下的CPU或其它资源消耗,可以修改执行频率参数为某一固定值;再比如我们希望执行更长时间以观察性能的平均情况,则可以修改执行时长的参数。如何修改?gtestx考虑灵活性提供了2种方式。
方式一,使用带_OPT后缀的参数扩展宏(实际所有的gtestx宏都有一个_OPT版本),这种方式适用于某个特别的用例固定地需要一个特殊的频率或时长配置的场景。取一个例子说明:
PERF_TEST_OPT(MiscFeaturesTest, SysCallPerf, 10000, 5000)
{
getppid();
}
以上代码以10000 calls/s的频率执行,同时一共执行5000 ms。如果频率和时长其中某个参数不想修改,只需填入-1(表示默认值)。
方式二,通过命令行参数修改(这种方式可以override方式一的修改),只需在命令行上使用-hz及-time两个选项来分别指定执行频率和时长参数。这种方式适用于在执行时临时修改不同的参数以观测比较执行结果的场景。以下为一例:
./gtestx_examples --gtest_filters='SimpleTest.*' -hz=10000 -time=10000
实现和代码
gtestx的实现原理并不复杂,它其实是hack了gtest的用例宏,在用例层之上又包装一层性能测试逻辑。
如有兴趣,你可以在这里获取代码,其中也包括一些文档和示例代码。
小结
本文简单介绍了gtestx以及其常用的做性能单元测试的方法,希望对你的工作有所帮助。
转:C++ 性能测试支持的更多相关文章
- Now直播应用的后台服务器性能测试实践
版权声明:本文由Oliver原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/208 来源:腾云阁 https://www.q ...
- WebApi管理和性能测试工具WebApiBenchmarks
说到WebApi管理和测试工具其实已经非常多的了,Postman.Swagger等在管理和维护上都非常出色:在性能测试方面也有不少的工具如:wrk,bombardier,http_load和ab等等. ...
- APTM敏捷性能测试模型
随着应用系统的日趋复杂,仅在系统测试和验收测试阶段执行性能测试已经不能满足迟早发现和解决系统性能瓶颈的要求,Connie Smith博士和Lloyd Winlliams博士在他们提出 的软件性能工程( ...
- 一个小型的DBHelper的诞生(1)
一直想做一个自己的简单的 DBHelper .没有其他原因,只是其他的轮子用起来感觉太重了. 设计的大体思路如下: 大体方向: 生成一个简单版本的DB层,需要支持数据库 MySql,Oracle,Sq ...
- 基于mysql的基准测试
常用的基准测试工具介绍: mysql基准测试工具: mysqlslap,mysql自带的工具,对于性能测试不建议使用 特点: 可以模拟服务器负载,并输出相关统计信息 可以指定也可以自动生成查询语句 常 ...
- 基准测试工具:Wrk初识
最近和同事聊起常用的一些压测工具,谈到了Apache ab.阿里云的PTS.Jmeter.Locust以及wrk各自的一些优缺点和适用的场景类型. 这篇博客,简单介绍下HTTP基准测试工具wrk的基本 ...
- 新特性,推荐一款超强接口管理神器 Apifox
去年,在公众号给大家推荐了一款新面市不久的接口测试神器:Apifox,如果还未了解的读者,感兴趣的话可查阅原文:推荐一款技术人必备的接口测试神器:Apifox 为了照顾新进来的读者,且最近一年,Api ...
- IP网络主动测评系统——IT运维人员的好帮手
一.前 言 随着计算机网络的普及和快速发展,互联网已经融入到人们的衣食住行等方方面 面,如工作.购物.音视频聊天.视频会议.朋友圈.抖音.在线网游.网络电影 电视等.毫不夸张地说,现如今大部分人的绝大 ...
- 网络主动测评系统,IT网络运维管理的法宝!
随着计算机网络的普及和快速发展,互联网已经融入到人们的衣食住行等方方面面,如工作.购物.音视频聊天.视频会议.朋友圈.抖音.在线网游.网络电影电视等.毫不夸张地说,现如今大部分人的绝大多数时间都已经离 ...
随机推荐
- spring注解使用
一.各种注解方式 1.@Autowired注解(不推荐使用,建议使用@Resource) @Autowired可以对成员变量.方法和构造函数进行标注,来完成自动装配的工作.@Autowired的标注位 ...
- BestCoder Round #65 hdu5591(尼姆博弈)
ZYB's Game Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total ...
- [iOS UI进阶 - 3.2] 手势识别器UIGestureRecognizer
A.系统提供的手势识别器 1.敲击手势 UITapGestureRecognizer numberOfTapsRequired: 敲击次数 numberOfTouchesRequired: 同时敲 ...
- UVaLive 6855 Banks (水题,暴力)
题意:给定 n 个数,让你求最少经过几次操作,把所有的数变成非负数,操作只有一种,变一个负数变成相反数,但是要把左右两边的数加上这个数. 析:由于看他们AC了,时间这么短,就暴力了一下,就AC了... ...
- PicklingError: Can't pickle <type 'generator'>: it's not found as __builtin_
多进程传递 参数时,需要是python系统已知的,不然不知道怎么序列化
- c#与java webservice调用问题
问题描述一: c#调用java写的websrevice,对方接收到的参数与实际传的值不一致,返回的时候值也获取不到,为null. 该参数为普通的string类型,因此不存在类型转换的问题. 处理: 使 ...
- 发布方配ASP.NET网站服务器
方配ASP.NET网站服务器是一款简单,轻量,灵活的ASP.NET网站服务器,使用它可以无需安装复杂的IIS,直接就可以运行ASP.NET网站,使用非常简单,把exe文件拷贝到ASP.NET的网站目录 ...
- 处理get中的中文乱码情况
1 最基本的乱码问题.这个乱码问题是最简单的乱码问题.一般新会出现.就是页面编码不一致导致的乱码.<%@ page language="java" pageEncoding= ...
- delphi 获取 TreeView选中的文件路径
//获取 TreeView选中的文件路径 unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, G ...
- iOS开发——新特性篇&swift新特性(__nullable和__nonnull)
swift新特性(__nullable和__nonnull) 最近在看老师写代码的时候经常遇到两个陌生的关键字,但是当我在我的电脑上敲得时候就是敲不出,后来才知道这是为了swift与OC混编的时候产生 ...