伪共享(False Sharing)是指在多线程环境中,多个线程并发访问不同的变量,这些变量被缓存到同一个缓存行中,导致性能下降。尽管这些线程访问的是不同的变量,但由于它们位于同一个缓存行内,它们之间的访问会相互影响,产生不必要的缓存同步,从而影响程序性能。
伪共享的原因
伪共享的出现主要是由于以下几个原因:
-
CPU缓存行的存在:
- 现代CPU有多个层次的缓存(如L1、L2、L3缓存),这些缓存是为了加速数据访问。CPU缓存通常以缓存行(Cache Line)为单位来存储数据。
- 通常,缓存行的大小是64字节,意味着一个缓存行可以存储64字节的数据。
-
变量存储在相同的缓存行:
- 当多个线程访问不同的变量时,如果这些变量恰好被存储在同一个缓存行中,那么它们就会发生伪共享。
- 即使不同线程访问的是不同的变量,CPU也会把整个缓存行加载到缓存中。当一个线程修改缓存行中的某个变量时,其他线程也会受到影响,即使它们访问的是缓存行中的其他变量,因为整个缓存行会被标记为失效,从而导致频繁的缓存一致性操作(如缓存同步、无效化)。
-
缓存一致性协议:
- 为了确保不同CPU核心之间的数据一致性,CPU会使用缓存一致性协议(如MESI协议)。当一个线程修改了缓存行中的数据,其他线程会被通知缓存行已经发生变化,导致缓存失效和同步。
- 如果多个线程访问同一个缓存行中的不同变量,频繁的缓存失效和同步会导致性能下降,这就是伪共享的本质。
伪共享的表现
伪共享会导致以下问题:
- 性能下降:当多个线程频繁修改和读取存储在同一个缓存行中的数据时,会导致缓存行频繁地被失效和同步,增加了缓存一致性操作的开销,从而降低了程序的整体性能。
- CPU缓存争用:尽管访问的是不同的数据,但由于这些数据在同一个缓存行中,多个线程在并发访问时会导致缓存争用。
如何避免伪共享
为了避免伪共享,可以采取以下几种方法:
-
缓存行对齐:
- 使用适当的方式将共享变量对齐到不同的缓存行。可以通过
@sun.misc.Contended
注解或使用 @NoUnroll
注解来强制将数据放置到不同的缓存行中。
- 在一些场景下,可以通过手动填充“填充字节”或调整数据结构来避免多个变量位于同一缓存行。
-
Padding技术:
- 通过在共享变量之间插入“填充”变量来确保不同的变量位于不同的缓存行中。这种方式通过使用多余的内存来避免变量之间共享缓存行,降低缓存一致性协议带来的性能开销。
例如,如果一个变量占用64字节的缓存行,则可以在变量之间插入一些无用的空字段,以确保变量不被放置在同一个缓存行中。
-
设计数据结构:
- 将经常一起访问的数据放在一起,而将不常访问的数据放在不同的缓存行中。这种设计能够最大限度地避免伪共享的发生。