C++ SSE将数据复制到变量

C++ SSE将数据复制到变量,c++,sse,simd,C++,Sse,Simd,我正在优化一段代码,让屏幕上的粒子围绕重力场移动。为此,我们被告知使用SSE。现在,在重写了这段代码之后,我想知道是否有一种更简单/更小的方法将值存储回粒子数组中 以下是之前的代码: for (unsigned int i = 0; i < PARTICLES; i++) { m_Particle[i]->x += m_Particle[i]->vx; m_Particle[i]->y += m_Particle[i]->vy; } for(无符号

我正在优化一段代码,让屏幕上的粒子围绕重力场移动。为此,我们被告知使用SSE。现在,在重写了这段代码之后,我想知道是否有一种更简单/更小的方法将值存储回粒子数组中

以下是之前的代码:

for (unsigned int i = 0; i < PARTICLES; i++) {
    m_Particle[i]->x += m_Particle[i]->vx;
    m_Particle[i]->y += m_Particle[i]->vy;
}
for(无符号整数i=0;ix+=m_粒子[i]>vx;
m_粒子[i]>y+=m_粒子[i]>vy;
}
下面是后面的代码:

for (unsigned int i = 0; i < PARTICLES; i += 4) {
    // Particle position/velocity x & y
    __m128 ppx4 = _mm_set_ps(m_Particle[i]->x, m_Particle[i+1]->x,
                             m_Particle[i+2]->x, m_Particle[i+3]->x);
    __m128 ppy4 = _mm_set_ps(m_Particle[i]->y, m_Particle[i+1]->y,
                             m_Particle[i+2]->y, m_Particle[i+3]->y);
    __m128 pvx4 = _mm_set_ps(m_Particle[i]->vx, m_Particle[i+1]->vx,
                             m_Particle[i+2]->vx, m_Particle[i+3]->vx);
    __m128 pvy4 = _mm_set_ps(m_Particle[i]->vy, m_Particle[i+1]->vy,
                             m_Particle[i+2]->vy, m_Particle[i+3]->vy);

    union { float newx[4]; __m128 pnx4; };
    union { float newy[4]; __m128 pny4; };
    pnx4 = _mm_add_ps(ppx4, pvx4);
    pny4 = _mm_add_ps(ppy4, pvy4);

    m_Particle[i+0]->x = newx[3]; // Particle i + 0
    m_Particle[i+0]->y = newy[3];
    m_Particle[i+1]->x = newx[2]; // Particle i + 1
    m_Particle[i+1]->y = newy[2];
    m_Particle[i+2]->x = newx[1]; // Particle i + 2
    m_Particle[i+2]->y = newy[1];
    m_Particle[i+3]->x = newx[0]; // Particle i + 3
    m_Particle[i+3]->y = newy[0];
}
for(无符号整数i=0;ix,m_粒子[i+1]->x,
m_粒子[i+2]->x,m_粒子[i+3]->x);
__m128 ppy4=_mm_set_ps(m_粒子[i]>y,m_粒子[i+1]>y,
m_粒子[i+2]>y,m_粒子[i+3]>y);
__m128 pvx4=_mm_set_ps(m_Particle[i]>vx,m_Particle[i+1]>vx,
m_粒子[i+2]->vx,m_粒子[i+3]->vx);
__m128 pvy4=_mm_set_ps(m_粒子[i]->vy,m_粒子[i+1]->vy,
m_粒子[i+2]->vy,m_粒子[i+3]->vy);
并集{float newx[4];uu m128 pnx4;};
联合{float newy[4];uu m128 pny4;};
pnx4=_mm_add_ps(ppx4,pvx4);
pny4=_mm_add_ps(ppy4,pvy4);
m_粒子[i+0]->x=newx[3];//粒子i+0
m_粒子[i+0]->y=newy[3];
m_粒子[i+1]->x=newx[2];//粒子i+1
m_粒子[i+1]->y=newy[2];
m_粒子[i+2]->x=newx[1];//粒子i+2
m_粒子[i+2]>y=newy[1];
m_粒子[i+3]->x=newx[0];//粒子i+3
m_粒子[i+3]>y=newy[0];
}

它可以工作,但对于像向另一个值添加值这样简单的事情来说,它看起来太大了。在不改变m_粒子结构的情况下,有没有一种更短的方法可以做到这一点?

我采用了一种稍微不同的方法来简化:每次迭代处理2个元素,并将它们打包为(x,y,x,y),而不是像您那样(x,x,x)和(y,y,y,y)

如果在粒子类中x和y是连续的浮点,并且在32位上对齐字段,则将x作为双精度加载的单个操作实际上将加载两个浮点x和y

for (unsigned int i = 0; i < PARTICLES; i += 2) {
    __m128 pos = _mm_set1_pd(0); // zero vector
    // I assume x and y are contiguous in memory
    // so loading a double at x loads 2 floats: x and the following y.
           pos = _mm_loadl_pd(pos, (double*)&m_Particle[i  ]->x);
    // a register can contain 4 floats so 2 positions
           pos = _mm_loadh_pd(pos, (double*)&m_Particle[i+1]->x);

    // same for velocities
    __m128 vel = _mm_set1_pd(0);
           vel = _mm_loadl_pd(pos, (double*)&m_Particle[i  ]->vx);
           vel = _mm_loadh_pd(pos, (double*)&m_Particle[i+1]->vy);

    pos = _mm_add_ps(pos, vel); // do the math

    // store the same way as load
    _mm_storel_pd(&m_Particle[i  ]->x, pos);
    _mm_storeh_pd(&m_Particle[i+1]->x, pos);
}
for(无符号整数i=0;ix);
//一个寄存器可以包含4个浮点数,因此可以包含2个位置
pos=_mm_loadh_pd(pos,(double*)&m_粒子[i+1]->x);
//速度也一样
__m128水平=_mm_设置1_pd(0);
水平=_-mm_-loadl_-pd(位置,(双*)&m_-Particle[i]->vx);
vel=_-mm_-loadh_-pd(pos,(double*)和m_-Particle[i+1]->vy);
pos=_-mm_-add_-ps(pos,vel);//计算一下
//存储方式与加载方式相同
_mm_-storel_-pd(&m_-Particle[i]>x,pos);
_mm_-storeh_-pd(&m_-Particle[i+1]>x,pos);
}
另外,既然您提到了粒子,您是否打算使用OpenGL/DirectX绘制它们?如果是这样,您可以在GPU上更快地执行这种排列,同时避免从主内存到GPU的数据传输,因此在所有方面都是一个优势

如果情况并非如此,并且您打算留在CPU上,那么使用SSE友好的布局(如一个阵列用于位置,一个阵列用于速度)可能是一个解决方案:

struct particle_data {
    std::vector<float> xys, vxvys;
};
struct particle\u数据{
std::向量xys,vxvys;
};
但它的缺点是要么破坏您的体系结构,要么需要从当前结构数组复制到临时结构数组。计算速度会更快,但额外的副本可能会超过这一点。只有基准测试才能显示


最后一个选择是牺牲一点性能并按原样加载数据,并使用SSE shuffle指令在每次迭代中本地重新排列数据。但可以说这会使代码更难维护。

我采用了一种稍微不同的方法来简化:每次迭代处理2个元素,并将它们打包为(x,y,x,y),而不是像您那样(x,x,x)和(y,y,y,y)

如果在粒子类中x和y是连续的浮点,并且在32位上对齐字段,则将x作为双精度加载的单个操作实际上将加载两个浮点x和y

for (unsigned int i = 0; i < PARTICLES; i += 2) {
    __m128 pos = _mm_set1_pd(0); // zero vector
    // I assume x and y are contiguous in memory
    // so loading a double at x loads 2 floats: x and the following y.
           pos = _mm_loadl_pd(pos, (double*)&m_Particle[i  ]->x);
    // a register can contain 4 floats so 2 positions
           pos = _mm_loadh_pd(pos, (double*)&m_Particle[i+1]->x);

    // same for velocities
    __m128 vel = _mm_set1_pd(0);
           vel = _mm_loadl_pd(pos, (double*)&m_Particle[i  ]->vx);
           vel = _mm_loadh_pd(pos, (double*)&m_Particle[i+1]->vy);

    pos = _mm_add_ps(pos, vel); // do the math

    // store the same way as load
    _mm_storel_pd(&m_Particle[i  ]->x, pos);
    _mm_storeh_pd(&m_Particle[i+1]->x, pos);
}
for(无符号整数i=0;ix);
//一个寄存器可以包含4个浮点数,因此可以包含2个位置
pos=_mm_loadh_pd(pos,(double*)&m_粒子[i+1]->x);
//速度也一样
__m128水平=_mm_设置1_pd(0);
水平=_-mm_-loadl_-pd(位置,(双*)&m_-Particle[i]->vx);
vel=_-mm_-loadh_-pd(pos,(double*)和m_-Particle[i+1]->vy);
pos=_-mm_-add_-ps(pos,vel);//计算一下
//存储方式与加载方式相同
_mm_-storel_-pd(&m_-Particle[i]>x,pos);
_mm_-storeh_-pd(&m_-Particle[i+1]>x,pos);
}
另外,既然您提到了粒子,您是否打算使用OpenGL/DirectX绘制它们?如果是这样,您可以在GPU上更快地执行这种排列,同时避免从主内存到GPU的数据传输,因此在所有方面都是一个优势

如果情况并非如此,并且您打算留在CPU上,那么使用SSE友好的布局(如一个阵列用于位置,一个阵列用于速度)可能是一个解决方案:

struct particle_data {
    std::vector<float> xys, vxvys;
};
struct particle\u数据{
std::向量xys,vxvys;
};
但它的缺点是要么破坏您的体系结构,要么需要从当前结构数组复制到临时结构数组。计算速度会更快,但额外的副本可能会超过这一点。只有基准测试才能显示

最后一种选择是牺牲一点性能,按原样加载数据,并使用SSE shuffle指令重新排列数据
struct Particles4
{
    __m128 x;
    __m128 y;
    __m128 xv;
    __m128 yv;
};

Particles4 particles[PARTICLES / 4];