在clang中对函数进行矢量化

11

我正试图使用clang根据此clang参考文献对以下函数进行向量化。它获取一个字节数组的向量,并根据此RFC应用掩码。

static void apply_mask(vector<uint8_t> &payload, uint8_t (&masking_key)[4]) {
  #pragma clang loop vectorize(enable) interleave(enable)
  for (size_t i = 0; i < payload.size(); i++) {
    payload[i] = payload[i] ^ masking_key[i % 4];
  }
}
以下标志被传递给clang编译器:
-O3
-Rpass=loop-vectorize
-Rpass-analysis=loop-vectorize

然而,使用向量化时出现以下错误:

WebSocket.cpp:5:
WebSocket.h:14:
In file included from boost/asio/io_service.hpp:767:
In file included from boost/asio/impl/io_service.hpp:19:
In file included from boost/asio/detail/service_registry.hpp:143:
In file included from boost/asio/detail/impl/service_registry.ipp:19:
c++/v1/vector:1498:18: remark: loop not vectorized: could not determine number
      of loop iterations [-Rpass-analysis]
    return this->__begin_[__n];
                 ^
c++/v1/vector:1498:18: error: loop not vectorized: failed explicitly specified
      loop vectorization [-Werror,-Wpass-failed]

如何将这个for循环向量化?


1
这个循环看起来很容易向量化。你是否检查过编译器是否会在普通的-03下隐式地执行它? - Baum mit Augen
1
我使用了-Rpass-analysis=loop-vectorize标志进行了检查。它不会隐式地进行向量化,这就是为什么我添加了显式的#pragma标志的原因。 - rahul
1
我想知道这是否是一个别名问题 - 你能否尝试将 restrict(和/或 const)应用于 uint8_t (&masking_key)[4] - Paul R
1
@PaulR const 可能不会有帮助,因为一个非 const 数据可以有 const&。不过 restrict 值得一试。 - Baum mit Augen
1
使用按值传递的 std::array 作为键也可以消除所有潜在的别名问题。 - Baum mit Augen
显示剩余4条评论
1个回答

5
感谢 @PaulR 和 @PeterCordes。将循环展开4倍可以提高效率。
void apply_mask(vector<uint8_t> &payload, const uint8_t (&masking_key)[4]) {
  const size_t size = payload.size();
  const size_t size4 = size / 4;
  size_t i = 0;
  uint8_t *p = &payload[0];
  uint32_t *p32 = reinterpret_cast<uint32_t *>(p);
  const uint32_t m = *reinterpret_cast<const uint32_t *>(&masking_key[0]);

#pragma clang loop vectorize(enable) interleave(enable)
  for (i = 0; i < size4; i++) {
    p32[i] = p32[i] ^ m;
  }

  for (i = (size4*4); i < size; i++) {
    p[i] = p[i] ^ masking_key[i % 4];
  }
}

gcc.godbolt code


1
那其实是Paul的想法。:) - Baum mit Augen
2
展开循环是将其向量化的唯一方法,我在gcc.godbolt上尝试了所有编译器,没有一个能够在不展开循环的情况下进行向量化。 - CoffeDeveloper
1
这也是我发现的,我不得不将所有4个键放入uint32_t中。(但我使用了memcpy而不是重新解释转换; 无论哪种方式在x86上都可以完全优化。)你的godbolt链接没有启用优化,并且函数上有static,因此我们无法看到函数本身的汇编代码。无论如何,这是您应该发布的godbolt链接。不要忘记更新您的答案中的代码,因为代码块中的代码仍然是无用的。 - Peter Cordes
@PeterCordes 非常感谢。这真的很有帮助。顺便问一下,您是否看到任何进一步优化的方法。分析仍然显示这是瓶颈。 - rahul
1
@rahul:如果你有Haswell或更新的版本,请使用-march=native进行构建,以便让它使用AVX2。我发布的链接中的汇编代码看起来对于SSE来说几乎是最优的,如果输入缓冲区是16B对齐的话,应该可以维持每个时钟一个16B向量。使用“-march=haswell”时,它执行相同的操作,但使用32B向量。使用“-march=sandybridge”时,它避免了不对齐的32B加载/存储,而是将它们分成16B的两半进行。 (将vextractf128提取到寄存器而不是直接提取到内存似乎是个坏主意。融合域uops的数量相同,但与vxorps竞争port5 :)) - Peter Cordes
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接