在我提出问题之前,只需要一些背景信息。
在C语言中,当您分配给变量时,您可以在概念上假设您刚刚在RAM中修改了一小段内存。
int a = rand(); //conceptually, you created and assigned variable A in ram
在汇编语言中,为了做同样的事情,你基本上需要存储在寄存器中的rand()的结果,以及指向" a"的指针。然后,您将执行存储指令以将寄存器内容输入ram。
例如,当您使用C ++编程时,在分配和操作值类型对象时,您通常甚至不必考虑它们的地址或它们将如何或何时存储在寄存器中。
使用SSE instrinics很奇怪,因为就概念性内存模型而言,它们似乎介于C语言和汇编语言之间的某处。
您可以调用加载/存储函数,并返回对象。像_mm_add这样的数学运算会返回一个对象,但我不清楚天气,除非你调用_mm_store,否则结果实际上会存储在对象中。
考虑以下示例:
inline void block(float* y, const float* x) const {
// load 4 data elements at a time
__m128 X = _mm_loadu_ps(x);
__m128 Y = _mm_loadu_ps(y);
// do the computations
__m128 result = _mm_add_ps(Y, _mm_mul_ps(X, _mm_set1_ps(a)));
// store the results
_mm_storeu_ps(y, result);
}
这里有很多临时物品。临时对象实际上不存在吗?是否只是以类似C的方式调用程序集instrunctions的语法糖?如果不是在最后执行store命令,你只是保留了结果,结果是否会超过语法糖,并且实际上会保存数据会发生什么?
TL:DR在使用SSE instrinsics时,我想如何考虑内存?
答案 0 :(得分:4)
__m128
变量可能位于寄存器和/或内存中。它与简单的float
或int
变量非常相似 - 编译器将决定哪些变量属于寄存器,哪些变量必须存储在内存中。通常,编译器会尝试将“最热”变量保留在寄存器中,其余变量保存在内存中。它还将分析变量的生命周期,以便寄存器可用于块内的多个变量。作为一个程序员,你不必太担心这个问题,但你应该知道你有多少寄存器,即8位XMM寄存器在32位模式下16位在64位模式下。保持您的变量使用低于这些数字将有助于尽可能地将所有内容保存在寄存器中。话虽如此,访问L1高速缓存中的操作数的代价并不比访问寄存器操作数大得多,所以如果事实证明难以保持寄存器中的所有内容,则不应过于依赖。这样做。
脚注:在使用内在函数时,关于SSE变量是在寄存器还是内存中的这种模糊性实际上非常有用,并且使得编写优化代码比使用原始汇编程序更容易 - 编译器执行了跟踪的粗略工作注册分配和其他优化,使您可以专注于使代码正常工作。
答案 1 :(得分:1)
矢量变量并不特殊。它们将溢出到内存并在以后需要时重新加载,如果编译器在优化循环时耗尽寄存器(或者通过对函数的函数调用,编译器可以""参见"到知道它没有触及矢量regs。)
实际上, gcc -O0
在设置时往往会存储到RAM中,而不是仅将__m128i
个变量保存在寄存器IIRC中。
您可以编写所有内在使用代码,而无需使用任何加载或存储内在函数,但是您必须由编译器决定如何以及何时移动数据周围。 (实际上,在某些程度上,由于编译器擅长优化内在函数,而且只要在任何使用内部负载的情况下随意吐出负载,你实际上仍然存在。)
如果不需要将值作为其他内容的输入,编译器会将加载折叠到内存操作数中以获取以下指令。但是,如果数据位于已知对齐的地址,或者使用了对齐的加载内在函数,则这是安全的。
我目前对加载内在函数的思考方式是向编译器传达对齐保证(或缺少对齐)的方法。" regular"如果与未对齐的128b内存操作数一起使用,则SSE(非AVX /非VEX编码)版本的向量指令会发生故障。 (即使在支持AVX,FWIW的CPU上。)例如,请注意,即使punpckl*
将其内存操作数列为m128
,因此具有对齐要求,即使它实际上只读取低64b。 pmovzx
将其操作数列为m128
。
无论如何,使用load
代替loadu
告诉编译器它可以将负载折叠成另一条指令的内存操作数,即使它无法证明它来自对齐的地址。
编译AVX目标机器将允许编译器将未对齐的载荷折叠到其他操作中,以利用uop微融合。
这是How to specify alignment with _mm_mul_ps的评论。
store
内在函数显然有两个目的:
__m128d
转换为double *
(不适用于整数情况)。为了混淆事物,AVX2引入了_mm256_storeu2_m128i (__m128i* hiaddr, __m128i* loaddr, __m256i a)
之类的东西,它将高/低两半存储到不同的地址。它可能编译为vmovdqu / vextracti128 ..., 1
序列。顺便说一句,我猜他们考虑到AVX512 vextracti128
,因为它使用0表示立即与vmovdqu
相同,但速度较慢且编码较长。