2014-03-07 35 views
1

我正在優化一段移動重力場周圍的粒子的代碼。爲此我們被告知使用SSE。現在,在重寫這一小段代碼之後,我想知道是否有更簡單/更小的方式將值存儲回粒子數組中。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 (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]; 
} 

它的工作原理,但它看起來是增加值到另一個值一樣簡單的方式過大。在不改變m_Particle結構的情況下是否有更簡單的方法?

+0

這是一種非常常見的情況,優化代碼(尤其是使用SIMD)會導致源代碼變得更大,更復雜。這看起來可能是「錯誤的」和違反直覺的,但只有當你試圖獲得最大的性能時,它纔是你必須接受的。 –

回答

3

有沒有理由,你爲什麼不能把xy並排在一個__m128,有些縮短了代碼:

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

    union { float pnew[4]; __m128 pnew4; }; 
    pnew4 = _mm_add_ps(pos, vel); 

    m_Particle[i+0]->x = pnew[0]; // Particle i + 0 
    m_Particle[i+0]->y = pnew[2]; 
    m_Particle[i+1]->x = pnew[1]; // Particle i + 1 
    m_Particle[i+1]->y = pnew[3]; 
} 

不過說真的,你遭遇的「結構的數組」與「數組結構」問題。上證所代碼工作更好地與一個「陣列的結構」,如:

struct Particles 
{ 
    float x[PARTICLES]; 
    float y[PARTICLES]; 
    float xv[PARTICLES]; 
    float yv[PARTICLES]; 
}; 

另一種選擇是一種混合的方法:

struct Particles4 
{ 
    __m128 x; 
    __m128 y; 
    __m128 xv; 
    __m128 yv; 
}; 

Particles4 particles[PARTICLES/4]; 

無論哪種方式將給簡單和更快的代碼比你的榜樣。

+0

感謝您的回答!它把我推向了正確的方向。我之前沒有想過使用'(x,y,x,y)'。 =)你的代碼有一點小錯誤,用於'pnew'的索引。 '_mm_set_ps'反轉順序,所以它應該保持'3,2,1,0'。我也喜歡這些其他的結構,我會稍後再看看它們! – Broxzier

+0

我不知道,我會解決它。現在你可以看到,我只有SSE的一點玩具體驗,而不是真實世界的代碼...... – japreiss

2

我去一個稍微不同的路線以簡化:處理每次迭代的2個元件和包裝它們作爲(X,Y,X,Y),而不是(X,X,X,X)和(Y,Y,Y, y)和你一樣。

如果在你的粒子類x和y是連續的浮點數並且你對齊32位的字段,加載一個x作爲double的單個操作實際上會加載兩個浮點數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); 
} 

另外,既然你提到了粒子,你打算用OpenGL/DirectX來繪製它們嗎?如果是這樣,你可以更快地在GPU上執行這種排列,同時也可以避免從主存到GPU的數據傳輸,所以這是所有方面的增益。

如果是這樣的情況並非如此,你打算留在CPU上,使用SSE友好佈局像一個陣列的位置,一個用於速度可能是一個解決辦法:

struct particle_data { 
    std::vector<float> xys, vxvys; 
}; 

但是,這將有要麼破壞你的體系結構,要麼從當前的結構數組拷貝到臨時的數組結構中。計算會更快,但附加的副本可能超過這一點。只有基準可以顯示...

最後一個選項是犧牲一點性能,因爲它是加載數據,並使用SSE整理指令在每次迭代局部重新排列數據。但可以說這會使代碼更難以維護。

1

對於性能設計,您應該避免處理結構數組,但您應該使用數組結構。

相關問題