如何實(shí)現(xiàn)一個(gè)簡易的benchmark

目標(biāo)

Benchmarking in C++中描述了一個(gè)簡易的benchmark,將逐步分析如何實(shí)現(xiàn)該benchmark,及模板化思路。

應(yīng)用場景

衡量算法其中一種方法是BigO,以n為因子,判斷隨著數(shù)量級(jí)增大耗時(shí)增加情況,而且考慮到屏蔽一些隨機(jī)擾動(dòng),需要進(jìn)行多次采樣比較。
譬如文中舉例比較std::vectorstd::list時(shí),進(jìn)行了10次采用,n的數(shù)量級(jí)從10的1次方直到10的5次方,輸出了2種情況下的耗時(shí)信息,再配合其可視化腳本輸出比較圖。

也就是說,這個(gè)benchmark將會(huì)支持多次采樣、多個(gè)因子、執(zhí)行多種算法測量。

最小實(shí)現(xiàn)

測量時(shí),需要在執(zhí)行前記錄當(dāng)前時(shí)間戳,然后執(zhí)行完成后根據(jù)結(jié)束的時(shí)間戳計(jì)算出耗時(shí)。

auto start = std::chrono::high_resolution_clock::now();
//執(zhí)行函數(shù);
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start);

以上的代碼得出的duration就是毫秒量級(jí)的耗時(shí)記錄結(jié)果。

一個(gè)最小的耗時(shí)測量實(shí)現(xiàn)如下:

//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();
    }
};

其中用到了完美轉(zhuǎn)發(fā)std::forward來處理參數(shù)傳遞。

使用方法為:

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

這樣就得到了n=1000時(shí)的耗時(shí)毫秒數(shù)。

支持多種測量

當(dāng)希望支持多種測量時(shí),就需要將測量結(jié)果存儲(chǔ)到map之中;實(shí)現(xiàn)時(shí)將其拆分為三塊:

  1. 執(zhí)行函數(shù)獲取耗時(shí)
  2. 耗時(shí)信息保存
  3. 整合執(zhí)行和保存

執(zhí)行函數(shù)獲取耗時(shí)

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));
    }
}

保存測量結(jié)果

//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;
};

整合執(zhí)行和保存

定義了experment_model來在構(gòu)造時(shí)完成函數(shù)執(zhí)行和結(jié)果保存:

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});

支持無因子測量

那么當(dāng)要測試的是普通函數(shù)時(shí),并沒有因子輸入,只是執(zhí)行了多次,那么就需要做出調(diào)整:

  1. 執(zhí)行無因子函數(shù)獲取耗時(shí)
  2. 耗時(shí)信息保存
  3. 提供無因子版experiment_model
  4. 提供無因子版benchmark.run

執(zhí)行無因子函數(shù)

//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);
}

調(diào)整的方式為移除測量時(shí)的因子參數(shù)輸入。

保存測量結(jié)果

測量結(jié)果只是一個(gè)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需要支持無因子,而其存儲(chǔ)的內(nèi)容為experiment_model,那么需要提供基類,保證experiment_model在兩種情況下都適用:

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

同樣,benchmark需要移除模板參數(shù)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)));
}

支持多分辨率測量

之前一直采用的是毫秒,在一些情況下函數(shù)執(zhí)行很快,需要采用微秒、納秒級(jí)別,那么就需要把之前寫死的std::chrono::milliseconds替換成模板參數(shù),同步修改所有位置:

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,但是對(duì)MSVC2013來講存在問題:
Time measurements with High_resolution_clock not working as intended
這個(gè)版本可以采用std::chrono::steady_clock,那么實(shí)現(xiàn)多種clock即可,與多分辨率的方式一致,提供模板參數(shù)TClock替換std::chrono::high_resolution_clock,并將默認(rèn)參數(shù)設(shè)置為std::chrono::high_resolution_clock

其它

  • 輸出結(jié)果
  • tic/toc
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 接著上節(jié) condition_varible ,本節(jié)主要介紹future的內(nèi)容,練習(xí)代碼地址。本文參考http:/...
    jorion閱讀 15,056評(píng)論 1 5
  • 接著上節(jié) atomic,本節(jié)主要介紹condition_varible的內(nèi)容,練習(xí)代碼地址。本文參考http://...
    jorion閱讀 8,645評(píng)論 0 7
  • 不講語言特性,只從工程角度出發(fā),個(gè)人覺得C++標(biāo)準(zhǔn)委員會(huì)在C++11中對(duì)多線程庫的引入是有史以來做得最人道的一件事...
    stidio閱讀 13,973評(píng)論 0 11
  • 失戀早已過33天,卻依然會(huì)想念,不知從何說起,不知何時(shí)才會(huì)結(jié)束,這種你已離開,向前向后向左右都不見你的迷離。
    四九月閱讀 213評(píng)論 0 0
  • 姓名:楊忠誠 公司:慧友冠源科技&272期六項(xiàng)精進(jìn)努力二組&廣東盛和塾稻牙二組 【日精進(jìn)打卡第27天】' 【知~學(xué)...
    楊忠誠閱讀 336評(píng)論 0 0

友情鏈接更多精彩內(nèi)容