根据运行时值选择一个 constexpr 并在热循环中使用它

choose a constexpr based on a runtime value and use it inside a hot loop

我需要遍历一个vector,读取每一个元素,映射到取模除法值。对于 power2 的除数,模除法很快。因此,我需要在运行时在 modmod_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);
  });
}

我的问题是,run1run2。我更喜欢 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));
  }
}

Demo

如果您不能将 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});
  }
}

Demo