我希望这不是一个非常愚蠢的问题,让我以后感到尴尬。但是,我一直对SIMD内置函数感到困惑,以至于我发现理解汇编代码比理解内置函数更容易。
所以,我主要的问题是关于使用SIMD内置数据类型,比如__m256
。为了简化问题,我的问题是关于像这样做的事情:
class PersistentObject
{
...
private:
std::vector<__m256, AlignedAlloc<__m256, 32>> data;
};
这是恶心的、可接受的,还是会在编译器生成最有效代码时出现问题?这就是我目前感到困惑的部分。当我遇到热点并耗尽所有其他立即选项时,我会尝试使用SIMD内置函数,并且始终试图撤消更改(我已经撤消了很多与SIMD相关的更改),但我处于一个不熟练的水平,因此对于如何持久地存储SIMD内置类型也存在疑问和困惑。这也让我意识到,我实际上并不理解这些内置函数在基本编译器级别上的工作原理。我的想法是将__m256视为抽象的YMM寄存器(不一定已分配)。当我看到load和store指令时,这个想法开始与我产生共鸣。我认为它们是提示编译器执行其寄存器分配的方法。但之前我不需要考虑得太多,因为我总是以临时方式使用SIMD类型:_mm256_load_ps到__m256,进行一些操作,将结果存回32位SPFP 256位对齐数组float[8]。我可以把__m256看作是一个YMM寄存器。
但最近我正在实现一种数据结构,该结构旨在围绕SIMD处理(一种简单的表示SoA方式中的一堆向量),在这种情况下,如果我可以主要使用__m256而不是不断地从float数组加载并在之后存储结果,则会变得更加方便。在一些快速测试中,至少MSVC似乎会发出适当的指令将我的内置函数映射到汇编(以及在我访问向量外的数据时进行适当对齐的加载和存储)。但这打破了我关于如何考虑所有这些东西的概念模型,因为持久存储这些东西意味着更像一个常规变量,但此时load/movs和store怎么办?
因此,我对我在头脑中构建的有关处理所有这些东西的方式的概念模型感到有些困惑,希望有经验的人能立即识别我思考这些东西的方式存在什么问题,并给我那个可以调试我的大脑的启示性答案。如果直接持久存储这些数据类型(意味着我们在使用_mm_load*之后某个时刻重新加载内存),是否可以接受?如果可以,那么我的概念模型有什么问题?
如果这个问题太愚蠢,请谅解!我对这些东西真的很陌生。
非常感谢迄今为止提供的有益评论!我想我应该分享更多细节,以使我的问题更加清晰。基本上,我正在尝试创建一个数据结构,它只是以SoA形式存储的矢量的集合而已:
xxxxxxxx....
yyyyyyyy....
zzzzzzzz....
这段内容主要是关于在hotspots(热点)中,关键循环具有连续访问模式,但同时非关键执行路径可能需要随机访问AoS格式的第5个3向量(x/y/z),此时我们不可避免地进行标量访问(如果效率不高,则完全没有问题,因为它们不是关键路径)。
在这种特殊情况下,从实现的角度来看,我认为使用__m256
持久存储和处理比float*
更方便。这样做可以避免在此情况下(在关键执行和大部分代码方面都是如此)频繁使用_mm_loads*
和_mm_stores*
的情况,而是使用SIMD内置函数。但我不确定这是否是一种良好的实践,或者仅保留__m256
作为短暂临时数据的预留位置,本地到某个函数,将一些浮点数加载到__m256中,执行一些操作,并将结果存回,就像我过去通常所做的那样。使用持久存储会更加方便,但我有点担心这种实现方式可能会出现一些优化器问题(尽管我还没有发现这种情况)。如果它们不会出现问题,那么我一直以来对这些数据类型的思考方式可能有点偏差。
所以,在这种情况下,如果完全没有问题,并且我们的优化器始终能够处理得很好,那么我就感到困惑了,因为我的思考方式一直都是需要在短暂的上下文中(本地函数内部)使用明确的_mm_load
和_mm_store
来帮助优化器,但这种方式是错误的!这让我有点失望,因为我认为它不应该正常运行! :-D
回答
有一些Mysticial的评论真的帮了我很多忙,也使我更加放心我想要做的事情是正确的。他的评论如下:
如果有帮助,我写了大约20万行代码,就像这样。换句话说,我将SIMD类型视为一级公民。没问题。编译器处理它们与任何其他基本类型无异。因此,它们没有任何问题。
优化器不是那么薄弱的。它们保持符合C / C ++标准的合理解释的正确性。除非您需要特殊类型的load / store内置函数(未对齐,非暂态,掩码等...),否则您不真正需要它们。
话虽如此,请随意编写自己的答案。信息越多越好!我真的希望提高对如何更自信地编写SIMD代码的基本理解,因为我现在处于一切都犹豫不决、总是自我怀疑的阶段。
回顾过去
再次感谢大家!现在我对围绕SIMD构建代码的设计更加清晰,更有信心了。由于某种原因,我对使用SIMD内置函数的优化器非常怀疑,认为我必须以尽可能低级的方式编写代码,并使这些加载和存储尽可能局限于有限的函数范围内。我想我的一些迷信可能是源于几十年前最初针对旧编译器编写SIMD内置函数,也许当时优化器需要更多的帮助,或者也许我一直不合理地迷信着。我有点像80年代人们看待C编译器的方式,到处放置register
提示之类的东西。
使用SIMD,我总是有着非常复杂的结果,并且倾向于始终感觉自己像个初学者,可能仅仅是因为混合的成功使我不愿使用它,这显著延迟了我的学习过程。最近我正在努力改正这一点,非常感谢大家的帮助!
__m256
(或任何其他SSE/AVX向量类型)的工作方式在概念上与int
相同。编译器会尽可能地将其保留在寄存器中,但如果需要,它可以自由地溢出到内存中,这可能是由于寄存器压力或语言规则要求(获取地址、别名等)导致的。 - Paul R