标签:lan move process sse 弱引用 finish rom fine 小程序
这里的 Bencher 指的是 C++ 代码性能测试工具。
下面是一点儿碎碎念
真的,C++ 要是有统一的包管理工具就好了。现在不但库的使用比较杂,还没有什么好的教材,真有一种旧时候手艺人的风格,大概得有个师父带着才能高效。如果我在写 Rust 的话,就直接上 Criterion 了,还管什么自己造轮子呢(当然我承认是我自己懒得去研究 Google Benchmark 怎么搞了)?
话说回来,现在写出的这个 Bencher 其实和最初的版本大相径庭。我一开始设计的有五个模块,但不断推敲后,最终变成了现在这样。设计真的不是那么简单呀。
上一篇文章里的 Timer 功能还是太简陋了,如果想比对多组数据,甚至可视化结果,那么 Timer 就不够看了。所以我需要更多的轮子。虽然说不要 Reinvent the wheel,但是用作学习的话,我想不失为好的方法。
口上说着要更多功能,但我并不打算扩展 Timer。毕竟它只是个 Timer,给它加上其它奇怪的功能不太好。甚至,说实话,我觉得那个求 Average 的功能都有些多余。
Benchmark
,主要的接口。一个 Benchmark 唯一拥有一个 Viewer
,Benchmark 的数据会发送到 Viewer 并显示出来。Viewer
,负责展示数据,甚至是数据可视化(如果有的话!Viewer 的实现里或许可以持有对 Benchmark 的弱引用,从而实现 Lazy Evaluation 等。但无论如何在我的设计里是 Benchmark 有 Viewer 的所有权。理想上来看,应该是 Benchmark 单向依赖于 Viewer。我或许需要再想想更好的结构。但目前这样应该是够用了。
// seideun\include\benchmark\Benchmark.hpp
//
// Created by Deuchie on 2020/8/20.
#ifndef SEIDEUN_BENCHMARK_HPP
#define SEIDEUN_BENCHMARK_HPP
#include "Viewer.hpp"
#include <chrono>
#include <memory>
namespace seideun::benchmark {
template <typename Signature>
class Benchmark;
template <typename R, typename... Args>
class Benchmark<R(Args...)> {
public:
virtual void set_viewer(std::unique_ptr<Viewer>&& viewer) = 0; // I‘m not sure if I need this function
virtual auto get_viewer() -> Viewer* = 0; // For viewer-specific operations
virtual void add_case(uint64_t amount_of_data, Args&&... args) = 0;
virtual void report() = 0;
virtual void set_title(std::string_view new_title) = 0;
};
}
#endif //SEIDEUN_BENCHMARK_HPP
// seideun\include\benchmark\Viewer.hpp
//
// Created by Deuchie on 2020/8/20.
//
#ifndef SEIDEUN_VIEWER_HPP
#define SEIDEUN_VIEWER_HPP
#include "BenchResult.hpp"
#include <string_view>
namespace seideun::benchmark {
class Viewer {
public:
/** General function to make the Viewer report current benchmark state
*
* Subclasses may well implement more specific operations. For example, designating format styles
*/
virtual void report() = 0;
/** Set the title of this viewer
*
* @param title
*/
virtual void set_title(std::string_view new_title) = 0;
/** Add a bench case to the viewer.
*
* It depends on the concrete viewers to provide specific representations of the amount of data (x) and the
* duration (y). A user of this interface has no idea how the data will be shown.
*
* @param amount_of_data an abstract value indicating how much the amount of data was.
* @param duration how long the bench took.
*/
virtual void add_case(BenchResult bench_result) = 0;
};
}
#endif //SEIDEUN_VIEWER_HPP
二者之间通过一个结构 BenchResult
传递消息。本来我是直接写在函数签名里的,但在检查的时候顺便抽出了一个类——或许这会是过度设计吧!
// seideun\include\benchmark\BenchResult.hpp
//
// Created by Deuchie on 2020/8/20.
#ifndef SEIDEUN_BENCHRESULT_HPP
#define SEIDEUN_BENCHRESULT_HPP
#include <chrono>
namespace seideun::benchmark {
class BenchResult {
public:
BenchResult(uint64_t amount_of_data, std::chrono::nanoseconds duration)
: amount_of_data_(amount_of_data), duration_(duration) {}
[[nodiscard]] auto amount_of_data() const -> uint64_t { return amount_of_data_; }
[[nodiscard]] auto duration() const -> std::chrono::nanoseconds { return duration_; }
private:
uint64_t amount_of_data_;
std::chrono::nanoseconds duration_;
};
}
#endif //SEIDEUN_BENCHRESULT_HPP
DefaultBencher
使用输出到 cout
的 TextViewer
。一开始没想加 report
和 set_title
两个函数,但是如果让用户调用 get_viewer
的话,会暴露奇怪的接口。我可以把接口中的 add_case
声明成 private
然后再把 Benchmark
声明成友元,但现在这样应该也行,我就懒得改了。
// seideun\include\benchmark\TextViewer.hpp
//
// Created by Deuchie on 2020/8/20.
#ifndef SEIDEUN_TEXTVIEWER_HPP
#define SEIDEUN_TEXTVIEWER_HPP
#include "Viewer.hpp"
#include <ostream>
#include <memory>
#include <vector>
namespace seideun::benchmark {
/** A viewer that outputs report to the ostream it was created with.
*
* Has shared ownership of the ostream. This may make it a little complicated with stdout (it is static, so we have
* to provide a dummy deleter to prevent "deleting" stdout once the shared_ptr‘s are all destructed) but otherwise
* frees us from the burden of closing the stream at the right time manually.
*
* Not thread safe.
*/
class TextViewer : public Viewer {
public:
TextViewer() = delete;
explicit TextViewer(std::shared_ptr<std::ostream> output_stream);
void report() override;
void set_title(std::string_view new_title) override;
void add_case(BenchResult bench_result) override;
private:
std::shared_ptr<std::ostream> ostream_;
std::vector<BenchResult> bench_results_;
std::string title_;
};
}
#endif //SEIDEUN_TEXTVIEWER_HPP
TextViewer
不是模板类,我顺便把实现分离到 cpp 文件里了:
// seideun/src/benchmark/TextViewer.cpp
//
// Created by Deuchie on 2020/8/20.
#include "benchmark/TextViewer.hpp"
#include "fmt/format.h"
namespace seideun::benchmark {
TextViewer::TextViewer(std::shared_ptr<std::ostream> output_stream) : ostream_(std::move(output_stream)) {}
void TextViewer::report() {
(*ostream_) << fmt::format("# {}:\n", title_);
size_t id = 1;
for (auto const& i : bench_results_) {
(*ostream_) << fmt::format(
"Case {}: Data Amount: {}\tDuration: {}ns\n", id, i.amount_of_data(), i.duration().count());
++id;
}
}
void TextViewer::set_title(std::string_view new_title) {
title_ = new_title;
}
void TextViewer::add_case(BenchResult bench_result) {
bench_results_.emplace_back(bench_result); // this is trivially copiable
}
}
然后是 DefaultBencher
:
// seideun\include\benchmark\DefaultBencher.hpp
//
// Created by Deuchie on 2020/8/20.
#ifndef SEIDEUN_DEFAULTBENCHER_HPP
#define SEIDEUN_DEFAULTBENCHER_HPP
#include "Benchmark.hpp"
#include "TextViewer.hpp"
#include <iostream>
#include <functional>
namespace seideun::benchmark {
template <typename Signature>
class DefaultBencher;
template <typename R, typename... Args>
class DefaultBencher<R(Args...)> : public Benchmark<R(Args...)> {
public:
// Sooooo ugly
explicit DefaultBencher(std::function<R(Args...)> func, std::string_view title = "") :
func_(std::move(func)),
viewer_(new TextViewer(std::shared_ptr<std::ostream>(&std::cout, [](auto i){})))
{
viewer_->set_title(title);
}
void set_viewer(std::unique_ptr<Viewer>&& viewer) override { viewer_ = std::move(viewer); }
auto get_viewer() -> Viewer* override { return viewer_.get(); }
void report() override { viewer_->report(); }
void set_title(std::string_view new_title) override { viewer_->set_title(new_title); }
void add_case(uint64_t amount_of_data, Args&&... args) override {
using namespace std::chrono;
auto const start = high_resolution_clock::now();
func_(std::forward<Args>(args)...);
auto const time_elapsed = duration_cast<nanoseconds>(high_resolution_clock::now() - start);
viewer_->add_case(BenchResult(amount_of_data, time_elapsed)); // Inform the viewer of the new bench result
}
private:
std::function<R(Args...)> func_; // function to bench
std::unique_ptr<Viewer> viewer_;
};
}
#endif //SEIDEUN_DEFAULTBENCHER_HPP
说实话这种没有确定输出的组件,我不知道怎么写测试……就简单写个小程序试试。
#include "benchmark/DefaultBencher.hpp"
#include <cmath>
#include <fstream>
#include <random>
int func(int a) {
using namespace std;
auto seed = a * 987610523 + 15636982;
default_random_engine e(seed);
uniform_int_distribution<uint64_t> u;
ofstream os("temp_output.txt", ios::app);
for (int i = 0; i != a; ++i) {
os << u(e) << ‘\t‘ << u(e) << ‘\n‘;
}
return seed;
}
int main() {
using namespace seideun::benchmark;
// 这里我没想出该如何利用自动推导,省略显式地写出模板参数的需要。
DefaultBencher<int(int)> test_bench(func, "Test Bench");
for (int i = 1; i != 10; ++i) {
test_bench.add_case(i * 1000, i * 1000);
}
test_bench.report();
}
下面是结果:
# Test Bench:
Case 1: Data Amount: 1000 Duration: 2583300ns
Case 2: Data Amount: 2000 Duration: 2742300ns
Case 3: Data Amount: 3000 Duration: 3626900ns
Case 4: Data Amount: 4000 Duration: 5078400ns
Case 5: Data Amount: 5000 Duration: 6092700ns
Case 6: Data Amount: 6000 Duration: 7197600ns
Case 7: Data Amount: 7000 Duration: 8216900ns
Case 8: Data Amount: 8000 Duration: 9797600ns
Case 9: Data Amount: 9000 Duration: 11040400ns
Process finished with exit code 0
嗯,即使只有默认实现,这东西用来简单判断一下算法复杂度什么的应该也够用了。
标签:lan move process sse 弱引用 finish rom fine 小程序
原文地址:https://www.cnblogs.com/seideun/p/13536573.html