如何实现一个简易的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 usedtemplate<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 resultstemplate<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 versiontemplate<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 versiontemplate<>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



文/长不胖的Garfield(简书作者)

上一篇: 深入理解C语言

下一篇: C++程序员学习发展方向分析和指导,学习参考!

分享到: 更多