簡體   English   中英

如何以及何時對齊緩存行大小?

[英]How and when to align to cache line size?

在Dmitry Vyukov用C ++編寫的優秀的有界mpmc隊列中見: http ://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue

他添加了一些填充變量。 我認為這是為了使其與高速緩存行對齊以提高性能。

我有一些問題。

  1. 為什么這樣做?
  2. 它是一種永遠有效的便攜式方法嗎?
  3. 在什么情況下最好使用__attribute__ ((aligned (64)))
  4. 為什么在緩沖區指針之前填充有助於提高性能? 不只是指針加載到緩存中,所以它實際上只是一個指針的大小?

     static size_t const cacheline_size = 64; typedef char cacheline_pad_t [cacheline_size]; cacheline_pad_t pad0_; cell_t* const buffer_; size_t const buffer_mask_; cacheline_pad_t pad1_; std::atomic<size_t> enqueue_pos_; cacheline_pad_t pad2_; std::atomic<size_t> dequeue_pos_; cacheline_pad_t pad3_; 

這個概念在gcc下是否適用於c代碼?

它以這種方式完成,因此修改不同字段的不同內核不必在其緩存之間反彈包含它們的緩存行。 通常,對於處理器訪問內存中的某些數據,包含它的整個緩存行必須位於該處理器的本地緩存中。 如果它正在修改該數據,那么該緩存條目通常必須是系統中任何緩存中的唯一副本(MESI / MOESI樣式的緩存一致性協議中的獨占模式)。 當單獨的內核嘗試修改恰好存在於同一緩存線上的不同數據時,從而浪費時間來回移動整條線,這就是所謂的錯誤共享

在您給出的特定示例中,一個核心可以將條目入隊(讀取(共享) buffer_和寫入(不包括) enqueue_pos_ ),而另一個核心可以排隊(共享buffer_和獨占dequeue_pos_ ),而不會在另一個核心的高速緩存行上停止核心停頓。

開頭的填充意味着buffer_buffer_mask_最終在同一個緩存行上,而不是分成兩行,因此需要雙倍的內存流量才能訪問。

我不確定這項技術是否完全可移植。 假設每個 cacheline_pad_t本身將與64字節(其大小)高速緩存行邊界對齊,因此隨后的任何內容都將在下一個高速緩存行上。 據我所知,C和C ++語言標准只需要整個結構,因此它們可以很好地存在於數組中,而不會違反任何成員的對齊要求。 (看評論)

attribute方法將更加特定於編譯器,但可能會將此結構的大小減半,因為填充將限於將每個元素四舍五入為完整的高速緩存行。 如果有很多這樣的話,這可能是非常有益的。

同樣的概念適用於C和C ++。

在處理中斷或高性能數據讀取時,您可能需要與高速緩存行邊界對齊,每個高速緩存行通常為64個字節,並且在使用進程間套接字時必須使用它們。 使用Interprocess套接字時,控制變量不能分布在多個高速緩存行或DDR RAM字中,否則會導致L1,L2等或高速緩存或DDR RAM用作低通濾波器並過濾掉中斷數據! 那很不好!!! 這意味着當你的算法很好並且有可能讓你瘋狂時,你會遇到奇怪的錯誤!

DDR RAM幾乎總是以128位字(DDR RAM字)讀取,這是16字節,因此環形緩沖區變量不應分布在多個DDR RAM字中。 有些系統確實使用64位DDR RAM字,技術上你可以在16位CPU上獲得32位DDR RAM字,但在這種情況下會使用SDRAM。

人們也可能只想對在高性能算法中讀取數據時最小化使用的高速緩存行數感興趣。 在我的例子中,我開發了世界上最快的整數到字符串算法(比之前最快的算法快40%),我正在努力優化Grisu算法,這是世界上最快的浮點算法。 為了打印浮點數,你必須打印整數,所以為了優化Grisu我實現的一個優化是我將Grisu的查找表(LUT)高速緩存行對齊到15個高速緩存行,這是相當奇怪,它實際上是這樣對齊的。 這將從.bss部分(即靜態內存)中獲取LUT並將它們放置到堆棧(或堆,但堆棧更合適)。 我沒有對此進行基准測試,但是提起它很好,而且我學到了很多,加載值的最快方法是從i-cache而不是d-cache加載它們。 不同之處在於i-cache是​​只讀的,並且具有更大的緩存行,因為它是只讀的(2KB是教授引用我的一次)。 所以你實際上要從數組索引中降低性能,而不是像這樣加載一個變量:

int faster_way = 12345678;

與較慢的方式相反:

int variables[2] = { 12345678, 123456789};
int slower_way = variables[0];

不同之處在於, int variable = 12345678將通過從函數的開頭偏移到i-cache中的變量從i-cache行加載,而slower_way = int[0]將從較小的d-加載緩存行使用更慢的數組索引。 正如我剛剛發現的這個特別巧妙的實際上正在減慢我和許多其他的整數到字符串算法。 我這樣說是因為你可能會通過緩存對齊只讀數據進行優化。

通常在C ++中,您將使用std::align函數。 我建議不要使用此功能,因為它不能保證最佳工作 這是最快的一種與高速緩存行對齊的方法,這是我的作者,這是一個無恥的插件:

Kabuki Toolkit內存對齊算法

namespace _ {
/* Aligns the given pointer to a power of two boundaries with a premade mask.
@return An aligned pointer of typename T.
@brief Algorithm is a 2's compliment trick that works by masking off
the desired number of bits in 2's compliment and adding them to the
pointer.
@param pointer The pointer to align.
@param mask The mask for the Least Significant bits to align. */
template <typename T = char>
inline T* AlignUp(void* pointer, intptr_t mask) {
  intptr_t value = reinterpret_cast<intptr_t>(pointer);
  value += (-value ) & mask;
  return reinterpret_cast<T*>(value);
}
} //< namespace _

// Example calls using the faster mask technique.

enum { kSize = 256 };
char buffer[kSize + 64];

char* aligned_to_64_byte_cache_line = AlignUp<> (buffer, 63);

char16_t* aligned_to_64_byte_cache_line2 = AlignUp<char16_t> (buffer, 63);

這是更快的std :: align替換:

inline void* align_kabuki(size_t align, size_t size, void*& ptr,
                          size_t& space) noexcept {
  // Begin Kabuki Toolkit Implementation
  intptr_t int_ptr = reinterpret_cast<intptr_t>(ptr),
           offset = (-int_ptr) & (align - 1);
  if ((space -= offset) < size) {
    space += offset;
    return nullptr;
  }
  return reinterpret_cast<void*>(int_ptr + offset);
  // End Kabuki Toolkit Implementation
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM