根据运行时值选择一个 constexpr 并在热循环中使用它
choose a constexpr based on a runtime value and use it inside a hot loop
我需要遍历一个vector
,读取每一个元素,映射到取模除法值。对于 power2 的除数,模除法很快。因此,我需要在运行时在 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
与 run2
。我更喜欢 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});
}
}
我需要遍历一个vector
,读取每一个元素,映射到取模除法值。对于 power2 的除数,模除法很快。因此,我需要在运行时在 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
与 run2
。我更喜欢 run2
因为它易于阅读并且没有代码重复。但是当我在 godbolt (https://godbolt.org/z/3ov59rb5s) 中同时检查时,AFAIU,run1
内联优于 run2
.
那么,有没有更好的方法来编写 run
函数而不影响性能?
跟进:
我 运行 对此很熟悉。 https://quick-bench.com/q/zO5YJtDMbnd10pk53SauOT6Bu0g
好像是clang,没什么区别。但是对于 GCC (10.3),run1
.
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});
}
}