我最近以内在函数的形式使用了x86 SIMD指令(SSE1234).我发现令人沮丧的是,SSE ISA有几个简单的指令,只能用于浮点数或仅用于整数,但理论上它们应该同样适用于两者.例如,float和double向量都有指令从地址(movhps,movhpd)加载更高的64位128位向量,但是没有这样的整数向量指令.
我的问题:
在整数向量上使用浮点指令时是否有任何理由期望性能受到影响,例如使用movhps将数据加载到整数向量?
我写了几个测试来检查,但我认为他们的结果不可信.编写一个正确的测试来探索这些事情的所有极端情况真的很难,特别是在这里可能涉及指令调度时.
相关问题:
其他平凡相似的东西也有几个基本相同的指令.例如,我可以使用por,orps或orpd按位OR .任何人都可以解释这些附加说明的目的是什么?我想这可能与应用于每条指令的不同调度算法有关.
我想添加一个SSE寄存器的四个组件来获得一个浮点数.这是我现在这样做的方式:
float a[4];
_mm_storeu_ps(a, foo128);
float x = a[0] + a[1] + a[2] + a[3];
Run Code Online (Sandbox Code Playgroud)
是否有直接实现此目的的SSE指令?
我目前正在开发一个开源的3D应用程序框架的C++(用C++ 11).我自己的数学库的设计类似于XNA数学库,同时考虑了SIMD.但目前它并不是很快,而且它在内存中存在问题,但在另一个问题上更多.
几天前我问自己为什么要编写自己的SSE代码.编译器还可以在启用优化时生成高优化代码.我也可以使用GCC的" 向量扩展 " .但这一切都不是真正的便携式.
我知道当我使用自己的SSE代码时,我有更多的控制权,但通常这种控制是不公平的.
SSE的一个大问题是使用动态内存,这在内存池和面向数据的设计的帮助下,尽可能地受到限制.
现在问我的问题:
我应该使用裸SSE吗?也许是封装的.
__m128 v1 = _mm_set_ps(0.5f, 2, 4, 0.25f);
__m128 v2 = _mm_set_ps(2, 0.5f, 0.25f, 4);
__m128 res = _mm_mul_ps(v1, v2);
Run Code Online (Sandbox Code Playgroud)或者编译器应该做脏工作吗?
float v1 = {0.5f, 2, 4, 0.25f};
float v2 = {2, 0.5f, 0.25f, 4};
float res[4];
res[0] = v1[0]*v2[0];
res[1] = v1[1]*v2[1];
res[2] = v1[2]*v2[2];
res[3] = v1[3]*v2[3];
Run Code Online (Sandbox Code Playgroud)或者我应该使用SIMD和其他代码吗?就像具有SIMD操作的动态容器类一样,需要额外的load和store指令.
Pear3D::Vector4f* v1 = new Pear3D::Vector4f(0.5f, …Run Code Online (Sandbox Code Playgroud)许多SSE"mov"指令指定它们正在移动浮点值.例如:
为什么这些指令只是说它们移动32位或64位值?如果它们只是移动位,为什么指令指定它们是浮点值?当然,无论你是否将这些位解释为浮点,它们都能工作吗?
(注意:虽然这个问题是关于"存储"的,但"加载"情况具有相同的问题并且是完全对称的.)
SSE内在函数提供_mm_storeu_pd具有以下签名的函数:
void _mm_storeu_pd (double *p, __m128d a);
Run Code Online (Sandbox Code Playgroud)
所以,如果我有两个双精度矢量,并且我想将它存储到两个双精度数组中,我可以使用这个内在函数.
但是,我的矢量不是两个双打; 它是两个64位整数,我想将它存储到两个64位整数的数组中.也就是说,我想要一个具有以下签名的函数:
void _mm_storeu_epi64 (int64_t *p, __m128i a);
Run Code Online (Sandbox Code Playgroud)
但内在函数没有提供这样的功能.他们最接近的是_mm_storeu_si128:
void _mm_storeu_si128 (__m128i *p, __m128i a);
Run Code Online (Sandbox Code Playgroud)
问题是这个函数需要一个指针__m128i,而我的数组是一个数组int64_t.通过错误类型的指针写入对象违反了严格的别名,并且肯定是未定义的行为.我担心我的编译器现在或将来会重新排序或以其他方式优化存储,从而以奇怪的方式破坏我的程序.
要清楚,我想要的是一个我可以这样调用的函数:
__m128i v = _mm_set_epi64x(2,1);
int64_t ra[2];
_mm_storeu_epi64(&ra[0], v); // does not exist, so I want to implement it
Run Code Online (Sandbox Code Playgroud)
以下是创建此类功能的六次尝试.
void _mm_storeu_epi64(int64_t *p, __m128i a) {
_mm_storeu_si128(reinterpret_cast<__m128i *>(p), a);
}
Run Code Online (Sandbox Code Playgroud)
这似乎有我担心的严格别名问题.
void _mm_storeu_epi64(int64_t *p, __m128i a) {
_mm_storeu_si128(static_cast<__m128i *>(static_cast<void *>(p)), a);
}
Run Code Online (Sandbox Code Playgroud)
这是我尝试使用SSE加速的示例C代码,两个数组是3072元素长的双精度数,如果我不需要双精度,可以将其下放到浮点数.
double sum = 0.0;
for(k = 0; k < 3072; k++) {
sum += fabs(sima[k] - simb[k]);
}
double fp = (1.0 - (sum / (255.0 * 1024.0 * 3.0)));
Run Code Online (Sandbox Code Playgroud)
无论如何,我目前的问题是如何在SSE寄存器中执行fabs步骤为double或float,以便我可以将整个计算保留在SSE寄存器中,以便它保持快速,并且我可以通过部分展开此循环来并行化所有步骤.
以下两行有什么区别?
__m128 x = _mm_load_ps((float *) ptr);
__m128 y = _mm_load_pd((double *)ptr);
Run Code Online (Sandbox Code Playgroud)
换句话说,为什么有这么多不同的_mm_load_xyz指令,而不是通用的__m128 _mm_load(const void *)?
假设我想添加两个缓冲区并存储结果.两个缓冲区已经分配了16byte对齐.我找到了两个如何做到这一点的例子.
第一个是使用_mm_load将数据从缓冲区读入SSE寄存器,执行add操作并存储回结果寄存器.到现在为止,我会这样做.
void _add( uint16_t * dst, uint16_t const * src, size_t n )
{
for( uint16_t const * end( dst + n ); dst != end; dst+=8, src+=8 )
{
__m128i _s = _mm_load_si128( (__m128i*) src );
__m128i _d = _mm_load_si128( (__m128i*) dst );
_d = _mm_add_epi16( _d, _s );
_mm_store_si128( (__m128i*) dst, _d );
}
}
Run Code Online (Sandbox Code Playgroud)
第二个例子直接在内存地址上执行了添加操作,没有加载/存储操作.两缝都很好.
void _add( uint16_t * dst, uint16_t const * src, size_t n )
{
for( uint16_t const * end( dst + n …Run Code Online (Sandbox Code Playgroud) 在x86_64上使用gcc 4.4.5(是的......我知道它已经老了).出于兼容性原因,仅限于SSE2(或更早)的说明.
我认为应该是一个教科书案例,以获得预取的巨大好处.我有一个32位元素的数组("A"),它不是(也可能不是)按顺序排列的.这些32位元素是__m128i数据的较大数据数组("D")的索引.为"A"的各要素,我需要在"d"从适当的位置取__m128i数据,在其上执行的操作,并且将其存储回在"d"的相同位置.实际上D中的每个"条目"都是"SOME_CONST"__m128i的大.因此,如果A中的值为"1",则D中的索引为D [1*SOME_CONST].
由于"A"中的连续元素几乎不会指向"D"中的连续位置,因此我倾向于认为硬件预取器将会挣扎或无法完成任何有用的操作.
但是,我可以很容易地预测下一个我将要访问的位置,只需在"A"中向前看即可.足够的措辞......这里有一些代码.我对数据执行的操作是取__m128i的低64位并将其克隆到相同的高64位.首先是基本循环,没有多余的装饰......
// SOME_CONST is either 3 or 4, but this "operation" only needs to happen for 3
for ( i=0; i<arraySize; ++i )
{
register __m128i *dPtr = D + (A[i] * SOME_CONST);
dPtr[0] = _mm_shuffle_epi32( dPtr[0], 0 | (1<<2) | (0<<4) | (1<<6) );
dPtr[1] = _mm_shuffle_epi32( dPtr[1], 0 | (1<<2) | (0<<4) | (1<<6) );
dPtr[2] = _mm_shuffle_epi32( dPtr[2], 0 | (1<<2) | (0<<4) | (1<<6) );
// The immediate operand selects:
// …Run Code Online (Sandbox Code Playgroud) 我用静态数组编写了一些代码,它的矢量化很好.
float data[1024] __attribute__((aligned(16)));
Run Code Online (Sandbox Code Playgroud)
我想动态分配数组.我尝试过这样的事情:
float *data = (float*) aligned_alloc(16, size*sizeof(float));
Run Code Online (Sandbox Code Playgroud)
但是编译器(GCC 4.9.2)不再能够对代码进行矢量化.我假设这是因为它不知道指针数据是16字节对齐的.我得到的消息如下:
note: Unknown alignment for access: *_43
Run Code Online (Sandbox Code Playgroud)
我尝试在使用数据之前添加此行,但它似乎没有做任何事情:
data = (float*) __builtin_assume_aligned(data, 16);
Run Code Online (Sandbox Code Playgroud)
使用不同的变量并restrict没有帮助:
float* __restrict__ align_data = (float*) __builtin_assume_aligned(data,16);
Run Code Online (Sandbox Code Playgroud)
例:
#include <iostream>
#include <stdlib.h>
#include <math.h>
#define SIZE 1024
#define DYNAMIC 0
#define A16 __attribute__((aligned(16)))
#define DA16 (float*) aligned_alloc(16, size*sizeof(float))
class Test{
public:
int size;
#if DYNAMIC
float *pos;
float *vel;
float *alpha;
float *k_inv;
float *osc_sin;
float *osc_cos;
float *dosc1;
float *dosc2;
#else
float …Run Code Online (Sandbox Code Playgroud)