我需要遍历一个vector
,读取每个元素,并映射到模除值。模除法对于幂2的除法是快速的。所以,在运行时,我需要在mod
和mod_power2
之间进行选择。下面是一个粗略的提纲。请假定我正在使用模板访问向量。
位操作技巧取自https://graphics.stanford.edu/~seander/bithacks.html
static inline constexpr bool if_power2(int v) {
return v && !(v & (v - 1));
}
static inline constexpr int mod_power2(int val, int num_partitions) {
return val & (num_partitions - 1);
}
static inline constexpr int mod(int val, int num_partitions) {
return val % num_partitions;
}
template<typename Func>
void visit(const std::vector<int> &data, Func &&func) {
for (size_t i = 0; i < data.size(); i++) {
func(i, data[i]);
}
}
void run1(const std::vector<int> &v1, int num_partitions, std::vector<int> &v2) {
if (if_power2(num_partitions)) {
visit(v1,
[&](size_t i, int v) {
v2[i] = mod_power2(v, num_partitions);
});
} else {
visit(v1,
[&](size_t i, int v) {
v2[i] = mod(v, num_partitions);
});
}
}
void run2(const std::vector<int> &v1, int num_partitions, std::vector<int> &v2) {
const auto part = if_power2(num_partitions) ? mod_power2 : mod;
visit(v1, [&](size_t i, int v) {
v2[i] = part(v, num_partitions);
});
}
我的问题是,run1
vsrun2
。我更喜欢run2
,因为它易于阅读,没有代码重复。但是当我在godbolt (https://godbolt.org/z/3ov59rb5s), AFAIU中检查时,run1
比run2
内联得更好。
那么,有没有更好的方法来编写run
函数而不影响性能?
跟进:
我做了一个快速实验。https://quick-bench.com/q/zO5YJtDMbnd10pk53SauOT6Bu0g
看起来对于clang来说,没有区别。但是在GCC(10.3)中,run1
有2倍的性能增益。
问题"与cond ? mod_power2 : mod
不同的是,它是一个函数指针,更难内联。
不同的lambda没有共同的类型。使用类型擦除(如std::function
)有开销,而去虚拟化更难优化。
所以,我看到的唯一选项是能够以"更好的">方式书写run1
:
分解lambda的创建;你需要将mod
/mod_power2
转换为函子(否则我们会遇到与run2
Demo相同的问题):
void run3(const std::vector<int> &v1, int num_partitions, std::vector<int> &v2) {
auto make_lambda = [&](auto&& f){
return [&](std::size_t i, int v){ v2[i] = f(v, num_partitions); };
};
if (if_power2(num_partitions)) {
visit(v1, make_lambda(mod_power2));
} else {
visit(v1, make_lambda(mod));
}
}
演示如果不能将mod
/mod_power2
更改为函子,则在编译时创建一个函子而不是lambda来获取它们的地址:
template <int (*f)(int val, int num_partitions)>
struct Functor
{
std::vector<int> &v2;
int num_partitions;
void operator ()(size_t i, int v) const
{
v2[i] = f(v, num_partitions);
}
};
void run4(const std::vector<int> &v1, int num_partitions, std::vector<int> &v2) {
if (if_power2(num_partitions)) {
visit(v1, Functor<mod_power2>{v2, num_partitions});
} else {
visit(v1, Functor<mod>{v2, num_partitions});
}
}
演示