2010-06-22 65 views
8

我正在構建一個使用SSE內在函數的類層次結構,因此該類的某些成員需要16字節對齊。對於棧實例,我可以使用__declspec(align(#)),像這樣:防止在堆上未對齊的數據

typedef __declspec(align(16)) float Vector[4]; 
class MyClass{ 
... 
private: 
Vector v; 
}; 

現在,由於__declspec(align(#))是一個編譯指令,下面的代碼可能會導致矢量堆上未對齊的實例:

MyClass *myclass = new MyClass; 

這我知道我可以很容易地通過超載刪除運營商相應地使用_aligned_malloc_aligned_free。像這樣:

//inside MyClass: 
public: 
void* operator new (size_t size) throw (std::bad_alloc){ 
    void * p = _aligned_malloc(size, 16); 
    if (p == 0) throw std::bad_alloc() 
    return p; 
} 

void operator delete (void *p){ 
    MyClass* pc = static_cast<MyClass*>(p); 
    _aligned_free(p); 
} 
... 

到目前爲止好..但這裏是我的問題。請看下面的代碼:

class NotMyClass{ //Not my code, which I have little or no influence over 
... 
MyClass myclass; 
... 
}; 
int main(){ 
    ... 
    NotMyClass *nmc = new NotMyClass; 
    ... 
} 

由於MyClass的MyClass的實例靜態創建於NotMyClass的動態實例,MyClass的會比較一致,因爲Vector的__declspec(align(16))指令NMC的開始16字節。但是這是毫無價值的,因爲nmc是通過NotMyClass的新運算符在堆上動態分配的,這不會確保(肯定可能不是)16字節對齊。

到目前爲止,我只能想到2點的方法對如何處理這個問題:

  1. 防止MyClass的用戶能夠編譯下面的代碼:

    MyClass myclass; 
    

    意義, MyClass的實例只能使用new運算符動態創建,從而確保MyClass的所有實例都是使用MyClass的重載新實例動態分配的。我諮詢關於如何做到這一點的另一個線程,並得到了一些偉大的答案:不必在我的課矢量成員 C++, preventing class instance from being created on the stack (during compiltaion)

  2. 還原並只有指針向量作爲成員,我會分配和使用_aligned_malloc解除分配並分別在ctor和dtor中輸入_aligned_free。這種方法看起來很粗糙,容易出錯,因爲我不是編寫這些類的唯一程序員(MyClass派生自基類,許多這些類使用SSE)。

但是,既然這兩個解決方案都在我的團隊中被淹沒了,我會向您尋求不同解決方案的建議。

+0

使用未對齊的SSE加載/存儲說明不是一個選項? (如果計算不加載/存儲性能的影響占主導地位應該是微乎其微。) – 2010-06-22 18:16:26

+0

如果您的團隊使用調試版本,你可以嘗試添加斷言強制理智^ Walignment。 – ninjalj 2010-06-22 18:30:56

+0

@ inflagranti:不是。首先,在一些(很少見的情況下)加載/存儲操作是我們最關心的問題。但是這些通常在具有大數據尺度的動態分配向量上執行。這些成員通常是傳遞給該類的單個參數。考慮到程序員必須在一個128字節的向量上執行一個簡單的設置函數。 SSE設置功能僅適用於對齊的數據。不,我們不能使用這些向量作爲靜態成員 – eladidan 2010-06-22 18:31:35

回答

4

如果您設置了堆分配,另一個想法是在堆棧上分配並手動對齊(手動對齊在this SO post中討論)。我們的想法是分配保證包含所需大小的對齊區域的大小的數據(unsigned char),然後通過從最大偏移的區域(x+15 - (x+15) % 16x+15 & ~0x0F)向下取整找到對齊的位置。我在codepad(對於g++ -O2 -msse2)發佈了一個帶有矢量運算的此方法的工作示例。這裏有一些重要的位:

class MyClass{ 
    ... 
    unsigned char dPtr[sizeof(float)*4+15]; //over-allocated data 
    float* vPtr;       //float ptr to be aligned 

    public: 
     MyClass(void) : 
     vPtr(reinterpret_cast<float*>( 
      (reinterpret_cast<uintptr_t>(dPtr)+15) & ~ 0x0F 
     )) 
     {} 
    ... 
}; 
... 

該構造函數確保vPtr對齊(注意類聲明中成員的順序很重要)。

這種方法的工作原理(包含類的堆/棧分配與對齊無關),是portabl-ish(我認爲大多數編譯器提供了一個指針大小爲uint uintptr_t),並且不會泄漏內存。但它不是特別安全的(即一定要保持一致指針下副本有效,等),廢物(幾乎)儘可能多的內存,因爲它使用,有的可能會發現令人反感的reinterpret_casts。

的對齊操作/未對齊數據的問題風險可以通過在一個矢量對象封裝這樣的邏輯,從而控制進入對準指針,並確保它被在施工對齊和保持有效來基本消除。

+0

+1。絕對是值得探索的解決方案。我肯定會將這種行爲封裝在類中以使其可用。儘管如此,內存消耗開銷可能會使其失去資格。我現在傾向於封裝一個只能在堆上分配並且完成的Vector類。這樣我就不必強迫MyClass進行任何對齊的行爲,並且我將問題所在的解決方案封裝在Vector中。 – eladidan 2010-06-23 09:50:58

+0

這是我在遇到'aligned_storage'之前使用的。我只是無法適應任意對齊要求,因爲有點欺騙:(。 – 2010-06-23 15:01:15

1

您可以使用「放置新的。」

void* operator new(size_t, void* p) { return p; } 

int main() { 
    void* p = aligned_alloc(sizeof(NotMyClass)); 
    NotMyClass* nmc = new (p) NotMyClass; 
    // ... 

    nmc->~NotMyClass(); 
    aligned_free(p); 
} 

當然,在銷燬對象時,通過調用析構函數然後釋放空間,需要小心。你不能只調用刪除。你可以使用不同的函數shared_ptr <>自動處理;它取決於處理shared_ptr(或指針的其他包裝)的開銷對您是否有問題。

+0

這對我來說不起作用,因爲NotMyClass實例是在我的代碼範圍之外創建的,並且很難強制程序員使用新的位置,就像我不想強制NotMyClass重載它的新運算符 – eladidan 2010-06-23 09:32:16

+0

在這種情況下,我不明白你的問題,你似乎在問如何影響不受你控制的程序員,如果是這樣的話,最好的答案可能是「很好地問他們做正確的事情」。對齊你分配的內存很容易,但獲得你不控制的代碼更難。 – janm 2010-06-23 10:23:07

+0

這很難,但我(希望)並非不可能。NotMyClass不關心對齊,也不應該關心對齊,這不僅僅是類問題,對我來說一個好的解決方案是確保無論在哪裏或怎樣,我的類的一個實例總是真正的16字節對齊。我不需要NotMyClass也是16字節對齊的。並迫使它看起來很不愉快 – eladidan 2010-06-23 11:38:44

0

即將到來的C++ 0x標準提出了處理原始內存設施。它們已被合併到VC++ 2010中(在tr1命名空間中)。

std::tr1::alignment_of // get the alignment 
std::tr1::aligned_storage // get aligned storage of required dimension 

這些都是類型,你可以使用它們像這樣:

static const floatalign = std::tr1::alignment_of<float>::value; // demo only 

typedef std::tr1::aligned_storage<sizeof(float)*4, 16>::type raw_vector; 
     // first parameter is size, second is desired alignment 

然後你可以聲明你的類:

class MyClass 
{ 
public: 

private: 
    raw_vector mVector; // alignment guaranteed 
}; 

最後,你需要一些投地操縱它(這是原始內存直到現在):

float* MyClass::AccessVector() 
{ 
    return reinterpret_cast<float*>((void*)&mVector)); 
} 
+0

我們使用英特爾的C++編譯器版本。 11.1,它還沒有tr1的支持。我嘗試使用boost的tr1擴展的建議,但遇到了同樣的問題。一切工作正常,直到我使用新的操作符在堆上分配NotMyClass。我承認我一般不熟悉C++ 0x,並且特意使用aligned_storage(直到閱讀您的答案時纔會知道它)。你可以擴展如何aligned_storage分配數據?我對它是如何設置以完成堆棧和堆棧上的對齊... – eladidan 2010-06-23 09:39:41

+0

對內部我不太瞭解,我只使用它,不幸的是,從來沒有真正挖掘過代碼。 – 2010-06-23 14:59:19

+0

@eladidan - 至少在gcc 4.5中,'aligned_storage'是通過'__attribute __((__ aligned __((...))))'在tr1 :: type_traits中實現的,這相當於您的__declspec。 – academicRobot 2010-06-23 16:50:00