我使用一些使用一些容器来存储数据的类;有带有多维容器的类。这些类重载operator ()
以索引数据。我在循环中经常使用这些对象,并希望对它们进行矢量化。GCC 无法直接对它们进行矢量化;它说“在基本块中找不到 SLP 机会”并取消矢量化。
我将如何对我的代码进行矢量化?
我还没有检查过其他编译器,因为我希望它可以被少数几个正在使用的著名编译器向量化。
我使用一些使用一些容器来存储数据的类;有带有多维容器的类。这些类重载operator ()
以索引数据。我在循环中经常使用这些对象,并希望对它们进行矢量化。GCC 无法直接对它们进行矢量化;它说“在基本块中找不到 SLP 机会”并取消矢量化。
我将如何对我的代码进行矢量化?
我还没有检查过其他编译器,因为我希望它可以被少数几个正在使用的著名编译器向量化。
首先,如果您打算成功地矢量化您的循环,我同意您必须“非常密切地管理您的记忆”的评论。如果不知道这一点 - 请参阅此答案末尾的脚注,以获取有关内存对齐的非常简短和肤浅的介绍。
然而,即使你的记忆力很好,还有另一种可能会阻碍你。Georg Hager e Gerhard Wellein,著名书籍“Introduction to High Performance Computing for Scientists and Engineer”的作者,明确指出 C++ 运算符重载可能会阻止循环向量化
用他们自己的话来说:
"(....) STL 可以通过以下方式定义此运算符(改编自 GNU ISO C++ 库源代码):
const T& operator[](size_t __n) const{ return *(this->_M_impl._M_start + __n); }
尽管这看起来很简单,可以有效地内联,但当前的编译器拒绝将 SIMD 向量化应用于上述求和循环。因此,单层抽象,在这种情况下是重载的索引运算符,可以防止创建最佳循环代码。”
一位好朋友说服我,这实际上不再适用于 stl 容器,因为编译器可以消除与operator[]
. 但是,您似乎编写了自己的容器,因此您必须检查编译器是否可以消除与您自己相关的间接层operator()
!一个好的交叉检查是为自己提供一种直接使用容器保存的底层数组的方法(意思是:编写一个类似的成员函数std::vector.data()
并将 C 指针用作循环内的“迭代器”)。
关于内存对齐的脚注:
问题:假设您要矢量化c[i] = a[i] + b[i]
.
第一个事实: size(double)
= 8 字节 = 64 位。
第二个事实:有一条汇编指令可以读取内存中的 2 个双精度数据并将它们放在 128 位寄存器中 => 使用一条汇编指令您可以读取 2 个双精度数据 => 他们可以读取a[0]
然后和!a[1]
b[0]
b[1]
第三个事实:当您在寄存器上应用指令时,您会double
同时对 2 个 64 位求和。
问题是,只有当和位于 16 的倍数的内存地址中时,程序集才能同时读取a[0]
和(如果不是,他可以测试和是否对齐等等)。这就是为什么内存可能成为阻碍矢量化的问题的原因。要解决这个问题,您必须编写容器分配器,以保证容器的第一个元素将被写入 16 的倍数的内存地址。a[1]
a[0]
b[0]
a[1]
b[1]
更新: 此答案提供了有关如何编写对齐内存的分配器的详细说明。
更新 2:学习如何编码分配器的另一个有用答案