2016-10-15 33 views
1

假設我有一個分配器my_allocator,當調用allocate(n)時,它將始終爲n+x(而不是n)元素分配內存。使用內存超過std :: vector的末尾使用自定義的過分配分配器

我可以savely承擔的範圍內[data()+n, data()+n+x)內存(對於std::vector<T, my_allocator<T>>)可訪問/有效期爲進一步利用基本面的情況下(即放置新的或SIMD加載/存儲(只要沒有重新分配)?

注意:我知道過去data()+n-1的所有內容都是未初始化的存儲空間。使用情況是使用自定義分配器的基本類型向量(無論如何都沒有構造函數),以避免在引發simd時出現特殊情況在向量處的內部函數my_allocator應分配1)正確對齊的存儲,並具有2)大小爲所用寄存器大小的倍數。


爲了讓事情一點點清晰:

比方說,我有兩個向量,我想將它們添加:

std::vector<double, my_allocator<double>> a(n), b(n); 
// fill them ... 
auto c = a + b; 
assert(c.size() == n); 

如果從my_allocator獲得的存儲現在分配對齊存儲和如果sizeof(double)*(n+x)始終是所使用的SIMD寄存器大小的倍數(並因此是每個寄存器的值的倍數),我假設我可以做類似

for(size_t i=0u; i<(n+x); i+=y) 
{ // where y is the number of doubles per register and and divisor of (n+x) 
    auto ma = _aligned_load(a.data() + i); 
    auto mb = _aligned_load(b.data() + i); 
    _aligned_store(c.data() + i, _simd_add(ma, mb)); 
} 

其中我不必關心任何特殊情況,比如未對齊的加載或來自n的未被y分割的積壓。

但是,矢量仍然只包含n值,並且可以像大小爲n的矢量一樣處理。

+0

你爲什麼想要?如果您需要n + x空間,請將大小設置爲n + x。 –

+0

@ChrisDodd:我需要尺寸爲'n'的矢量。我只想做一些循環展開,但不要確保不會超調(或者不必檢查是否還有任何未展開的方式需要處理)。 – Pixelchemist

+0

剛剛使用'reserve'(在展開op之前,例如'reserve(n + x)'),而不是搞亂分配器,怎麼辦?如果你沒有在'size'之後存儲任何有用的東西,爲什麼'我<(n + x)'而不是簡單的'i FranMowinckel

回答

1

對於您的用例,您不應該有任何疑問。但是,如果您決定在額外空間中存儲任何有用的東西,並且允許矢量的大小在其生命週期內發生更改,那麼您可能會遇到處理重新分配可能性的問題 - 您將如何將額外數據從舊分配給新分配,因爲重新分配是由於分別調用allocate()deallocate()而導致的,它們之間沒有直接聯繫?


編輯(地址添加到問題的代碼)

在我原來的答覆我的意思是,你不應該有任何問題,訪問由超過什麼是你的分配器分配額外的字節請求。但是,將數據寫入存儲器範圍內的數據,這些數據超出了矢量對象當前使用的範圍,但屬於未修改分配所跨越的範圍,這就要求解決問題。 std::vector的實現可以從分配器向內存請求比通過其功能暴露的內存更多的內存並且將輔助數據存儲在未使用的區域中。雖然這是高度理論化的,但不考慮這種可能性意味着打開未定義行爲的大門。

考慮矢量的分配的以下可能的佈局:

---====================++++++++++------......... 
  1. === - 向量的使用容量
  2. +++ - 向量
  3. ---的未使用的容量 - 由載體過度分配(但未顯示爲其容量的一部分)
  4. ... - 過度分配給您的人定位器

不得在區域2(---)和3(+++)中寫入任何內容。你所有的寫入必須限制在區域4(...),否則你可能會損壞重要的位。

+0

我不想實際存儲那裏的東西。如果我認爲是正確的,我會(並希望能夠)確保我能夠處理或防止重新分配情況,但暫時不是這個意圖。目前我想執行循環展開。 – Pixelchemist

+0

@Pixelchemist查看更新後的答案 - 事實上,您要展開循環的方式包含UB威脅。 – Leon

+0

有效的一點。這幾乎排除了。 – Pixelchemist

2

如果您嘗試解決的問題是允許通過SIMD內在函數或展開的循環有效處理底層內存,或者兩者兼而有之,則您不一定需要分配超出所用量的內存只是爲了將分配大小「四捨五入」爲矢量寬度的倍數。

有幾種方法可以處理這種情況,並且您提到了一對夫婦,例如處理前導和尾隨部分的特殊導入和導出代碼。實際上存在兩個不同的問題 - 處理數據不是矢量寬度的倍數的事實,以及處理(可能)未對齊的起始地址。您的超額分配方法正在解決第一個問題 - 但可能有更好的方法...

大多數SIMD代碼在實踐中可以簡單地讀取超出處理區域的末尾。有人可能會認爲這在技術上是UB - 但是當使用SIMD內在函數時,您已經冒險超越了標準C++的內核。實際上,這種技術在標準庫中是already widely used,所以它被編譯器和庫維護者隱式支持。這也是一般處理SIMD代碼的標準方法,所以你可以確信它不會突然中斷。

他們關鍵使它的工作是觀察,如果你能在某個位置N有效地閱讀,甚至一個字節,那麼任何一個自然對齊讀取任何大小的不會觸發故障。當然,您仍然需要忽略或處理您在正式分配區域末尾閱讀的數據 - 但無論如何,您需要使用「分配額外」方法來執行此操作,對吧?根據算法的不同,您可能會掩蓋掉無效數據,或者在SIMD部分完成後排除無效數據(例如,如果您正在搜索一個字節,如果您在分配的區域後面找到一個字節,則它與「不是找到「)。

爲了使這項工作,你需要以對齊的方式閱讀,但這可能是你已經想要做的事情,我認爲。您可以安排先分配內存,或者在開始時進行重疊讀取(即首先進行一個未對齊的讀取,然後全部對齊與未對齊的部分重疊的第一個對齊的讀取),或者使用相同的技巧作爲的尾部讀之前的數組(與爲什麼這是安全的相同的推理)。此外,還有各種技巧來請求對齊的內存,而無需編寫自己的分配器。

總的來說,我的建議是嘗試避免寫一個自定義分配器。除非代碼被嚴格包含,否則您可能會遇到各種陷阱,包括其他代碼對你的記憶分配方式作出錯誤的假設,以及Leon在答案中提到的各種其他缺陷。此外,使用自定義分配器會禁用標準容器算法使用的一堆優化,除非您在任何地方都使用它,因爲其中許多優化只適用於使用相同分配器的容器。另外,當我實際上實現自定義分配器時,我發現它在理論上是個不錯的主意,但是在所有編譯器中以相同的方式得到很好的支持,這有點太模糊不清。現在,隨着時間的推移,編譯器已經變得更加合規了(我主要關注你,Visual Studio),並且模板支持也得到了改進,所以也許這不是問題,但我認爲它仍然屬於「do只有你必須「。

請記住,自定義分配器不構成 - 你只得到一個!如果您項目中的其他人想出於其他原因使用自定義分配器,他們將無法做到這一點(儘管您可以協調並創建組合分配器)。

這個question我早些時候問過 - 也是SIMD的動機 - 涵蓋了很多關於閱讀過程的安全性(並且隱含地,在開始之前)的理由,並且可能是一個開始的好地方,如果你是考慮這一點。


技術上,限制任何對準讀取到頁的大小,這在4K或更大的充足爲任何當前面向向量的通用的ISA的。

在這種情況下,我正在做如果不是SIMD,但基本上避免malloc(),並允許部分堆棧上和連續快速分配用於與許多小的節點的容器。

相關問題