目標
Benchmarking in C++中描述了一個簡易的benchmark嘹叫,將逐步分析如何實現(xiàn)該benchmark嫩絮,及模板化思路丛肢。
應用場景
衡量算法其中一種方法是BigO,以n為因子剿干,判斷隨著數(shù)量級增大耗時增加情況蜂怎,而且考慮到屏蔽一些隨機擾動,需要進行多次采樣比較置尔。
譬如文中舉例比較std::vector
和std::list
時杠步,進行了10次采用,n的數(shù)量級從10的1次方直到10的5次方,輸出了2種情況下的耗時信息幽歼,再配合其可視化腳本輸出比較圖朵锣。
也就是說,這個benchmark將會支持多次采樣试躏、多個因子猪勇、執(zhí)行多種算法測量。
最小實現(xiàn)
測量時颠蕴,需要在執(zhí)行前記錄當前時間戳泣刹,然后執(zhí)行完成后根據(jù)結束的時間戳計算出耗時。
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
就是毫秒量級的耗時記錄結果犀被。
一個最小的耗時測量實現(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();
}
};
其中用到了完美轉發(fā)std::forward
來處理參數(shù)傳遞椅您。
使用方法為:
auto oVal = measure<>::execution(CommonUse<std::vector<int>>,1000);
這樣就得到了n=1000
時的耗時毫秒數(shù)。
支持多種測量
當希望支持多種測量時寡键,就需要將測量結果存儲到map
之中掀泳;實現(xiàn)時將其拆分為三塊:
- 執(zhí)行函數(shù)獲取耗時
- 耗時信息保存
- 整合執(zhí)行和保存
執(zhí)行函數(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));
}
}
保存測量結果
//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
來在構造時完成函數(shù)執(zhí)行和結果保存:
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});
支持無因子測量
那么當要測試的是普通函數(shù)時员舵,并沒有因子輸入,只是執(zhí)行了多次藕畔,那么就需要做出調(diào)整:
- 執(zhí)行無因子函數(shù)獲取耗時
- 耗時信息保存
- 提供無因子版experiment_model
- 提供無因子版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ù)輸入马僻。
保存測量結果
測量結果只是一個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需要支持無因子注服,而其存儲的內(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í)行很快女淑,需要采用微秒、納秒級別辜御,那么就需要把之前寫死的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
,但是對MSVC2013來講存在問題:
Time measurements with High_resolution_clock not working as intended
這個版本可以采用std::chrono::steady_clock
擒权,那么實現(xiàn)多種clock即可苇本,與多分辨率的方式一致,提供模板參數(shù)TClock替換std::chrono::high_resolution_clock
,并將默認參數(shù)設置為std::chrono::high_resolution_clock
菜拓。
其它
- 輸出結果
- tic/toc