2012-09-19 54 views
23

我寫了一個使用大量SSE編譯器內在函數的3D矢量類。一切正常,直到我開始安裝具有3D矢量作爲新成員的類。我在發佈模式下遇到了奇怪的崩潰,但不是在調試模式下,反之亦然。SSE,內在函數和對齊

因此,我讀了一些文章,並認爲我需要將擁有3D矢量類的實例的類也對齊到16字節。於是我就在班級前面加_MM_ALIGN16__declspec(align(16))像這樣:

_MM_ALIGN16 struct Sphere 
{ 
    // .... 

    Vector3 point; 
    float radius 
}; 

這似乎在首先要解決的問題。但更改一些代碼後,我的程序又開始以奇怪的方式崩潰。我在網上搜索了一些,發現了一篇blog文章。我嘗試了作者Ernst Hot爲解決問題所做的工作,它對我也很有用。我加了new和delete操作符來我的班是這樣的:

_MM_ALIGN16 struct Sphere 
{ 
    // .... 

    void *operator new (unsigned int size) 
    { return _mm_malloc(size, 16); } 

    void operator delete (void *p) 
    { _mm_free(p); } 

    Vector3 point; 
    float radius 
}; 

恩斯特提到,這個形式給出可能是有問題的爲好,但他只是鏈接到它不沒有解釋爲什麼它可能會有問題存在了一個論壇。

所以我的問題是:

  1. 什麼是與定義運營商的問題?

  2. 爲什麼不將_MM_ALIGN16添加到類定義足夠?

  3. 處理SSE內在函數的對齊問題的最佳方法是什麼?

+0

在第一種情況下,你是在棧還是堆上分配結構?我不確定malloc會默認返回對齊的內存,而_mm_malloc肯定會 - 「一段時間後我的程序開始再次崩潰」是什麼意思?你的意思是在離開它後運行一段時間(以及它在做什麼)? – Thomas

+0

當我開始在堆上分配結構時,問題就開始了。隨着「一段時間」的句子,我的意思是說,它改變了代碼後開始崩潰。我想這個對齊是正確的,然後我把它摧毀了。我認爲malloc不會返回內存16字節對齊,這是我猜的問題。我的問題實際上是運營商方法的問題是什麼,以及使用SSE內在函數管理代碼的最佳方式是什麼。 –

+2

事實上,你不需要指定'Sphere'的對齊方式(使用這個'_MM_ALIGN16'的東西),因爲編譯器足夠聰明,可以看到'Sphere'有一個16對齊的成員並自動調整'Sphere' s的對齊要求(鑑於'Vector3'已正確對齊)。這就是爲什麼如果它已經有'__m128'成員,你不必明確地對齊Vector3。只有動態分配是一個問題,這可以通過重載'operator new/delete'來克服,就像寫在博客中一樣(通常還有其他的東西,比如專門用於'std :: allocator'的)。 –

回答

18

首先你要照顧兩種類型的內存分配的:

  • 靜態分配。要使自動變量正確對齊,您的類型需要適當的對齊方式(例如__declspec(align(16))__attribute__((aligned(16)))_MM_ALIGN16)。但幸運的是,如果類型的成員(如果有)提供的對齊要求不足,則只需要這些。所以你不需要這個Sphere,因爲你的Vector3已經正確對齊。如果您的Vector3包含__m128成員(這很可能,否則我會建議這樣做),那麼您甚至不需要它Vector3。所以你通常不必混淆編譯器特定的對齊屬性。

  • 動態分配。非常容易的部分。問題在於,C++在最底層使用了一種非類型無關的內存分配函數來分配任何動態內存。這隻能保證所有標準類型的正確對齊,這可能是16字節,但不能保證。

    爲了彌補,你必須重載內置的operator new/delete來實現你自己的內存分配,並在引擎蓋下使用對齊的分配函數,而不是老的malloc。重載operator new/delete本身就是一個話題,但並不像起初看起來那麼困難(雖然你的例子還不夠),你可以在this excellent FAQ question中看到它。

    不幸的是,您必須爲每種類型的任何成員都需要這樣做,這些成員需要非標準對齊,在您的案例中都是SphereVector3。但是你可以做些什麼來簡化它,只需要爲這些運算符創建一個具有適當重載的空基類,然後從該基類中派生所有必需的類。

    大多數人有時往往忘記的是,標準分配器std::alocator用於所有內存分配全球operator new,所以你的類型不會與標準集裝箱的工作(和std::vector<Vector3>不是罕見的用例)。你需要做的是製作你自己的標準一致性分配器並使用它。但爲了方便和安全,實際上最好是專門爲您的類型專門設置std::allocator(也許只是從您的自定義分配器派生出來),以便始終使用它,並且每次使用時都不需要關心使用合適的分配器std::vector。不幸的是,在這種情況下,你必須再次針對每種對齊的類型進行專門化,但是一個小小的邪惡宏可以幫助實現這一點

    另外,你必須使用全局operator new/delete而不是你自定義的,像std::get_temporary_bufferstd::return_temporary_buffer看出來的其他東西,並照顧那些如果neccessary。

不幸的是,還沒有對這些問題一個更好的方法,我想,除非你是本身對齊到16 一個平臺上,並瞭解這個。或者你可能只是重載全局的operator new/delete,以便每個內存塊總是對齊16個字節,並且不需要關心包含SSE成員的每個類的對齊,但我不知道這種方法的含義。在最糟糕的情況下,它只會導致浪費內存,但是通常不會在C++中動態分配小對象(儘管std::liststd::map對此可能會有不同的想法)。

所以總結起來:

  • 關愛的使用像__declspec(align(16))靜態存儲器正確對齊,但只有當它不被照顧的任何成員,這通常是這種情況。

  • 過載operator new/delete對於具有非標準對齊要求的成員的每種類型。

  • 製作一個cunstom符合標準的分配器,用於對齊類型的標準容器中,或者更好的是,針對每種對齊類型專門設置std::allocator


最後一些普遍性的建議。在執行許多向量操作時,通常只有在計算量大的塊中才能從SSE中獲利。爲了簡化所有這些對齊問題,尤其是關注包含Vector3的每種類型的對齊問題,製作特殊的SSE向量類型可能是一個很好的方法,並且只能在冗長的計算中使用它,使用正常的非-SSE矢量用於存儲和成員變量。

+0

來自C++ 11的'std :: aligned_storage'是否可以啓用所有這些,而不需要專門的調用約定? –

+1

@ graham.reeds而是'alignas'關鍵字。 'std :: aligned_storage'並不是真的需要,因爲'__m128'已經正確對齊,你寧願要'__m128'成員而不是'std :: aligned_storage'成員。但是,'alignas'是說'__declspec(align())'(或任何gcc喜歡的)的新平臺獨立方式,即使它們通常都不需要。但是,所有這些只會有助於靜態內存對齊。 –

1
  1. 問題與經營者是自己他們是不夠的。它們不影響堆棧分配,您仍然需要__declspec(align(16))

  2. __declspec(align(16))影響編譯器如何將對象放置在內存中,當且僅當它有選擇。對於new'ed對象,編譯器別無選擇,只能使用由operator new返回的內存。

  3. 理想情況下,使用編譯器來處理它們本身。爲什麼他們需要與double區別對待沒有理論上的理由。否則,請閱讀編譯器文檔瞭解變通方法。每個有障礙的編譯器都會有自己的一套問題,因此也有自己的一套解決方法。

+0

謝謝!從Christian Rau的評論我認爲'__declspec(align(16))'已經過時了。我猜這部分取決於編譯器?我不確定我是否理解你答案的三個部分。 「通過本地處理」是什麼意思。我使用Visual Studio 2012 Express附帶的編譯器。 –

+1

@FairDinkumThinkum:我的意思是「編譯器本身處理它們」是一種編譯器,它可以像對齊FP類型一樣對齊SSE類型,即不需要程序員的幫助。你不需要'__declspec(align(8))'。我沒有VS2012,所以我無法確定它是否已經很聰明。 – MSalters

+1

您很可能也必須實現自定義分配器。 –

2

基本上,你需要確保你的矢量正確對齊,因爲SIMD向量類型通常具有比任何內置類型的更大的對齊要求。

,需要做以下幾件事:

  1. 確保當它是堆棧或結構部件上Vector3正確對齊。這是通過將__attribute__((aligned(32)))應用於Vector3類(或者編譯器支持的任何屬性)來完成的。請注意,您不需要將該屬性應用於包含Vector3的結構,這不是必要的也不夠(即,無需將其應用於Sphere)。

  2. 確保Vector3或其封閉結構在使用堆分配時正確對齊。這是通過使用posix_memalign()(或用於您的平臺的類似功能)而不是使用普通的malloc()operator new()來完成的,因爲後兩個對齊的內存類型(通常爲8或16字節)的內存不足以滿足SIMD類型。