如何实现一个简易的benchmark

目标

Benchmarking in C++中描述了一个简易的benchmark,将逐步分析如何实现该benchmark,及模板化思路。

应用场景

衡量算法其中一种方法是BigO,以n为因子,判断随着数量级增大耗时增加情况,而且考虑到屏蔽一些随机扰动,需要进行多次采样比较。
譬如文中举例比较std::vectorstd::list时,进行了10次采用,n的数量级从10的1次方直到10的5次方,输出了2种情况下的耗时信息,再配合其可视化脚本输出比较图。

也就是说,这个benchmark将会支持多次采样、多个因子、执行多种算法测量。

最小实现

测量时,需要在执行前记录当前时间戳,然后执行完成后根据结束的时间戳计算出耗时。

auto start = std::chrono::high_resolution_clock::now();
//执行函数;
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start);

以上的代码得出的duration就是毫秒量级的耗时记录结果。

一个最小的耗时测量实现如下:

//invoke function and return time used
template<typename TTime = std::chrono::milliseconds>
struct measure
{
    template<typename F, typename ...Args>
    static typename TTime::rep execution(F func, Args&&... args)
    {
        auto start = std::chrono::high_resolution_clock::now();
        func(std::forward<Args>(args)...);
        auto duration = std::chrono::duration_cast<TTime>(std::chrono::high_resolution_clock::now() - start);
        return duration.count();
    }
};

其中用到了完美转发std::forward来处理参数传递。

使用方法为:

auto oVal =  measure<>::execution(CommonUse<std::vector<int>>,1000);

这样就得到了n=1000时的耗时毫秒数。

支持多种测量

当希望支持多种测量时,就需要将测量结果存储到map之中;实现时将其拆分为三块:

  1. 执行函数获取耗时
  2. 耗时信息保存
  3. 整合执行和保存

执行函数获取耗时

struct measure
{
    //measure function implement
    template<typename F, typename TFactor>
    inline static auto duration(F&& callable, TFactor&& factor)
    {
        auto start = std::chrono::high_resolution_clock::now();
        std::forward<decltype(callable)>(callable)(std::forward<TFactor>(factor));
        return (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start));
    }
}

保存测量结果

//save results
template<typename TFactor>
struct experiment_impl
{
    std::string _fctName;
    std::map<TFactor,std::vector<std::chrono::milliseconds>> _timings;
    experiment_impl(const std::string& factorName):_fctName(factorName){};
 
protected:
    ~experiment_impl() = default;
};

整合执行和保存

定义了experment_model来在构造时完成函数执行和结果保存:

template<typename TFactor>
struct experment_model final :detail::experiment_impl<TFactor>
{
    //invoke function and save result
    template<typename F>
    experment_model(std::size_t nSample, F&& callable, const std::string& factorName, std::initializer_list<TFactor>&& factors)
        :experiment_impl<TFactor>(factorName)
    {
        for (auto&& factor:factors)
        {
            experiment_impl<TFactor>::_timings[factor].reserve(nSample);
            for (std::size_t i = 0 ; i < nSample ; i++)
            {
                experiment_impl<TFactor>::_timings[factor].push_back(measure::duration(std::forward<decltype(callable)>(callable),factor));
            }
        }
    }
 
};

封装成benchmark来支持多次、多因子、多种测量:

template<typename TFactor>
class benchmark
{
    std::vector<std::pair<std::string,std::unique_ptr<detail::experment_model<TFactor>>>> _data;
public:
    benchmark() = default;
    benchmark(benchmark const&) = delete;
public:
    template<class F>
    void run(const std::string& name, std::size_t nSample, F&& callable,
        const std::string& factorName, std::initializer_list<TFactor>&& factors)
    {
        _data.emplace_back(name,std::make_unique<detail::experment_model<TFactor>>(nSample,
            std::forward<decltype(callable)>(callable),factorName,
            std::forward<std::initializer_list<TFactor>&&>(factors)));
    }
}

使用方法

bmk::benchmark<std::size_t> bm;
bm.run("vector",10, CommonUse<std::vector<int>>,"number of elements",{10,100,1000,10000});
bm.run("list",10, CommonUse<std::list<int>>,"number of elements",{10,100,1000,10000});

支持无因子测量

那么当要测试的是普通函数时,并没有因子输入,只是执行了多次,那么就需要做出调整:

  1. 执行无因子函数获取耗时
  2. 耗时信息保存
  3. 提供无因子版experiment_model
  4. 提供无因子版benchmark.run

执行无因子函数

//measure function void version
template<typename F>
inline static auto duration(F&& callable)
{
    auto start = std::chrono::high_resolution_clock::now();
    std::forward<decltype(callable)>(callable)();
    return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start);
}

调整的方式为移除测量时的因子参数输入。

保存测量结果

测量结果只是一个vector<milliseconds>,提供experment_impl的偏特化版本:

//save result void version
template<>
struct experiment_impl<void>
{
    std::vector<std::chrono::milliseconds> _timings;
    experiment_impl(std::size_t nSample):_timings(nSample){};
protected:
    ~experiment_impl() = default;
};

提供无因子版experiment_model

//invoke function and save result [void version]
template<typename F>
experment_model(std::size_t nSample, F&& callable)
    :experiment_impl<void>(nSample)
{
    for (std::size_t i = 0; i < nSample; i++)
    {
        experiment_impl<TFactor>::_timings.push_back(measure::duration(std::forward<decltype(callable)>(callable)));
    }
}

提供无因子版benchmark.run

由于benchmark需要支持无因子,而其存储的内容为experiment_model,那么需要提供基类,保证experiment_model在两种情况下都适用:

struct experiment
{
    virtual ~experiment() {};
};
template<typename TFactor = void>
struct experment_model final :detail::experiment,detail::experiment_impl<TFactor>
{
  ......
};

同样,benchmark需要移除模板参数TFactor

class benchmark
{
    std::vector<std::pair<std::string,std::unique_ptr<detail::experiment>>> _data;
   ......
}

然后是无因子版的run

template<class F>
void run(const std::string& name, std::size_t nSample, F&& callable)
{
    _data.emplace_back(name, std::make_unique<detail::experment_model<>>(nSample,
        std::forward<decltype(callable)>(callable)));
}

支持多分辨率测量

之前一直采用的是毫秒,在一些情况下函数执行很快,需要采用微秒、纳秒级别,那么就需要把之前写死的std::chrono::milliseconds替换成模板参数,同步修改所有位置:

template<typename TTime ,typename TFactor>
struct experiment_impl
{
   ......
}
   ......
template<typename TTime>
struct measure
{
   ......
}
   ......
template<typename TTime,typename TFactor = void>
struct experment_model final :detail::experiment,detail::experiment_impl<TTime,TFactor>
{
   ......
}
   ......
template<typename TTime>
class benchmark
{
   ......
}

支持多种clock

在之前都采用的是std::chrono::high_resolution_clock,但是对MSVC2013来讲存在问题:
Time measurements with High_resolution_clock not working as intended
这个版本可以采用std::chrono::steady_clock,那么实现多种clock即可,与多分辨率的方式一致,提供模板参数TClock替换std::chrono::high_resolution_clock,并将默认参数设置为std::chrono::high_resolution_clock

其它

  • 输出结果
  • tic/toc
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 接着上节 condition_varible ,本节主要介绍future的内容,练习代码地址。本文参考http:/...
    jorion阅读 14,698评论 1 5
  • 接着上节 atomic,本节主要介绍condition_varible的内容,练习代码地址。本文参考http://...
    jorion阅读 8,432评论 0 7
  • 不讲语言特性,只从工程角度出发,个人觉得C++标准委员会在C++11中对多线程库的引入是有史以来做得最人道的一件事...
    stidio阅读 13,214评论 0 11
  • 失恋早已过33天,却依然会想念,不知从何说起,不知何时才会结束,这种你已离开,向前向后向左右都不见你的迷离。
    四九月阅读 179评论 0 0
  • 姓名:杨忠诚 公司:慧友冠源科技&272期六项精进努力二组&广东盛和塾稻牙二组 【日精进打卡第27天】' 【知~学...
    杨忠诚阅读 279评论 0 0