想象一下,我想要时间的M方法,以及N个时序方法(让我们称之为时钟实现) 1 。具体细节在这里并不重要,但我提到它所以我可以举一个具体的例子。
现在让我说我有一个模板化的计时方法:
typedef void (bench_f)(uint64_t);
template <bench_f METHOD, typename CLOCK>
uint64_t time_method(size_t loop_count) {
auto t0 = CLOCK::now();
METHOD(loop_count);
auto t1 = CLOCK::now();
return t1 - t0;
}
基本上它通过调用METHOD
来约束对CLOCK::now()
的调用并返回差异。另请注意,METHOD
不是作为函数指针传递,而是仅作为模板参数传递 - 因此您获得每个方法的唯一实例化,而不是一个,然后通过一个间接调用指针。
这适用于我的情况,因为时钟调用和被测方法都是直接静态调用(即汇编级别的call <function address>
)。
现在我想要测试N个方法(可能是50个)和M个时钟方法(可能是5个)。我想在编译时实例化所有M * N方法,以便我可以使用特定的时钟实现来调用所有测试方法。
现在,执行此操作的“标准”方法只是为被测方法和时钟实现传递函数指针(或实现虚函数的类),此时我只需要一个{{ 1}}方法,可以在运行时创建我想要的任何组合。在这种特殊情况下,间接调用的性能影响太大,所以我想要模板实例化,并且我愿意支付最终的二进制膨胀(例如,M * N = 250实例化我的数字组合)。
在运行时,我想获得一个N方法的列表,例如与特定时钟相结合。
我很好地明确列出了所有N方法和所有M个时钟,但我不想写出M * N实例(DRY和所有这些)。
1 我在这里使用 clock 这个词 - 一些“时钟”实际上可能测量与时间无关的方面,例如堆内存使用,或某些特定于应用程序的指标。
答案 0 :(得分:8)
template<bench_f* ...> struct method_list {};
template<class...> struct clock_list {};
using time_method_t = uint64_t (*)(size_t);
template<bench_f Method, class...Clocks>
constexpr auto make_single_method_table()
-> std::array<time_method_t, sizeof...(Clocks)> {
return { time_method<Method, Clocks>... };
}
template<bench_f*... Methods, class... Clocks>
constexpr auto make_method_table(method_list<Methods...>, clock_list<Clocks...>)
-> std::array<std::array<time_method_t, sizeof...(Clocks)>, sizeof...(Methods)> {
return { make_single_method_table<Methods, Clocks...>()... };
}
答案 1 :(得分:3)
要制作代码,您必须使用选项数量而不是产品的总和来编写线性,编写一次删除一层选项的模板函数。
e.g。
typedef uint64_t (*benchmark_runner)(size_t loop_count);
benchmark_runner all_runners[NMETHODS][NCLOCKS];
template <bench_f METHOD>
void fill_row(size_t bench_f_index)
{
benchmark_runner* it = &all_runners[bench_f_index][0];
*(it++) = &time_method<METHOD, FIRST_CLOCK>;
*(it++) = &time_method<METHOD, SECOND_CLOCK>;
*(it++) = &time_method<METHOD, THIRD_CLOCK>;
*(it++) = &time_method<METHOD, LAST_CLOCK>;
}
void fill_all()
{
int row = 0;
fill_row<BENCH_A>(row++);
fill_row<BENCH_B>(row++);
...
fill_row<BENCH_Z>(row++);
}