CPU缓存及伪共享

商务办公
在计算机系统中,CPU高速缓存是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。

 什么是CPU缓存

在计算机系统中,CPU高速缓存是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。

[[285785]]

当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

 

CPU缓存及伪共享

 

为什么需要CPU缓存

随着工艺的提升,最近几十年 CPU 的频率不断提升,而受制于制造工艺和成本限制,目前计算机的内存在访问速度上没有质的突破。因此,CPU 的处理速度和内存的访问速度差距越来越大,甚至可以达到上万倍。这种情况下传统的 CPU 直连内存的方式显然就会因为内存访问的等待,导致计算资源大量闲置,降低 CPU 整体吞吐量。同时又由于内存数据访问的热点集中性,在 CPU 和内存之间用较为快速而成本较高(相对于内存)的介质做一层缓存,就显得性价比极高了。

为什么需要有CPU多级缓存

  1. 各种寄存器,用来存储本地变量和函数参数,访问一次需要1cycle,耗时小于1ns;
  2. L1 Cache,一级缓存,本地 core 的缓存,分成 32K 的数据缓存 L1d 和 32k 指令缓存 L1i,访问 L1 需要3cycles,耗时大约 1ns;
  3. L2 Cache,二级缓存,本地 core 的缓存,被设计为 L1 缓存与共享的 L3 缓存之间的缓冲,大小为 256K,访问 L2 需要 12cycles,耗时大约 3ns;
  4. L3 Cache,三级缓存,在同插槽的所有 core 共享 L3 缓存,分为多个 2M 的段,访问 L3 需要 38cycles,耗时大约 12ns;

大致可以得出结论,缓存层级越接近于 CPU core,容量越小,速度越快,当 CPU 执行运算的时候,它先去 L1 查找所需的数据,再去 L2,然后是 L3,最后如果这些缓存中都没有,所需的数据就要去主内存拿。走得越远,运算耗费的时间就越长。

什么是缓存行

缓存行 (Cache Line) 便是 CPU Cache 中的最小单位,CPU Cache 由若干缓存行组成,一个缓存行的大小通常是 64 字节(这取决于 CPU),并且它有效地引用主内存中的一块地址。一个 Java 的 long 类型是 8 字节,因此在一个缓存行中可以存 8 个 long 类型的变量。

 

CPU缓存及伪共享

 

猜一下下面代码的执行时间:

public class ArrayLoop { 
 public static void main(String[] args) { 
 long[][] arr = new long[1024 * 1024][8]; 
 long sum = 0; 
 //横向遍历 
 long start = System.currentTimeMillis(); 
 for (int i = 0; i < 1024 *1024; i++) { 
 for (int j = 0; j < 8; j++) { 
 sum += arr[i][j]; 
 } 
 } 
 System.out.println("横向遍历耗时:" + (System.currentTimeMillis() - start) + "ms"); 
 //纵向遍历 
 start = System.currentTimeMillis(); 
 for (int i = 0; i < 8; i++) { 
 for (int j = 0; j < 1024 * 1024; j++) { 
 sum += arr[j][i]; 
 } 
 } 
 System.out.println("纵向遍历耗时:" + (System.currentTimeMillis() - start) + "ms"); 
 } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

在我电脑上的执行时间为:

横向遍历耗时:32ms

纵向遍历耗时:88ms

在程序运行的过程中,缓存每次更新都从主内存中加载连续的 64 个字节。因此,如果访问一个 long 类型的数组时,当数组中的一个值被加载到缓存中时,另外 7 个元素也会被加载到缓存中。

什么是伪共享

 

CPU缓存及伪共享

 

如果多个线程的变量共享了同一个 CacheLine,任意一方的修改操作都会使得整个 CacheLine 失效(因为 CacheLine 是 CPU 缓存的最小单位),也就意味着,频繁的多线程操作,CPU 缓存将会彻底失效,降级为 CPU core 和主内存的直接交互。

如何避免伪共享

使用了字节填充技术(空间换时间)解决伪共享;

public final class FalseSharing implements Runnable { 
 public final static int NUM_THREADS = 4; // change 
 public final static long ITERATIONS = 500L * 1000L * 1000L; 
 private final int arrayIndex; 
 private static VolatileLong[] longs = new VolatileLong[NUM_THREADS]; 
 static { 
 for (int i = 0; i < longs.length; i++) { 
 longs[i] = new VolatileLong(); 
 } 
 } 
 public FalseSharing(final int arrayIndex) { 
 this.arrayIndex = arrayIndex; 
 } 
 public static void main(final String[] args) throws Exception { 
 final long start = System.currentTimeMillis(); 
 runTest(); 
 System.out.println("duration = " + (System.currentTimeMillis() - start)); 
 } 
 private static void runTest() throws InterruptedException { 
 Thread[] threads = new Thread[NUM_THREADS]; 
 for (int i = 0; i < threads.length; i++) { 
 threads[i] = new Thread(new FalseSharing(i)); 
 } 
 for (Thread t : threads) { 
 t.start(); 
 } 
 for (Thread t : threads) { 
 t.join(); 
 } 
 } 
 public void run() { 
 long i = ITERATIONS + 1; 
 while (0 != --i) { 
 longs[arrayIndex].value = i; 
 } 
 } 
 public final static class VolatileLong { 
 public volatile long value = 0L; 
 public long p1, p2, p3, p4, p5, p6; // 填充,可以注释后对比测试 
 } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.

Java8 中实现字节填充

@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.FIELD, ElementType.TYPE}) 
public @interface Contended { 
 String value() default ""

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

注意需要同时开启 JVM 参数:-XX:-RestrictContended=false

@Contended 注解会增加目标实例大小,要谨慎使用。默认情况下,除了 JDK 内部的类,JVM 会忽略该注解。要应用代码支持的话,要设置 -XX:-RestrictContended=false,它默认为 true(意味仅限 JDK 内部的类使用)。当然,也有个 –XX: EnableContented 的配置参数,来控制开启和关闭该注解的功能,默认是 true,如果改为 false,可以减少 Thread 和 ConcurrentHashMap 类的大小。参加《Java性能权威指南》210 页。

ConcurrentHashMap 中,使用 @sun.misc.Contended 对静态内部类 CounterCell 进行修饰。另外还包括并发容器 Exchanger 也有相同的操作。

/* ---------------- Counter support -------------- */ 
/** 
 * A padded cell for distributing counts. Adapted from LongAdder 
 * and Striped64. See their internal docs for explanation. 
 */ 
@sun.misc.Contended static final class CounterCell { 
 volatile long value; 
 CounterCell(long x) { value = x; } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

Thread 线程类的源码中,使用 @sun.misc.Contended 对成员变量进行修饰。

// The following three initially uninitialized fields are exclusively 
// managed by class java.util.concurrent.ThreadLocalRandom. These 
// fields are used to build the high-performance PRNGs in the 
// concurrent code, and we can not risk accidental false sharing. 
// Hence, the fields are isolated with @Contended. 
/** The current seed for a ThreadLocalRandom */ 
@sun.misc.Contended("tlr"
long threadLocalRandomSeed; 
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */ 
@sun.misc.Contended("tlr"
int threadLocalRandomProbe; 
/** Secondary seed isolated from public ThreadLocalRandom sequence */ 
@sun.misc.Contended("tlr"
int threadLocalRandomSecondarySeed; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

参考:

https://juejin.im/post/5c471d75e51d45299a08b333

https://juejin.im/post/5cd644886fb9a032136fe6d7

责任编辑:武晓燕 来源: 今日头条
相关推荐

2022-12-12 08:39:09

CPUCache伪共享

2017-07-13 16:40:16

伪共享缓存行存储

2023-12-26 10:08:57

缓存伪共享修饰结构体

2019-01-15 14:44:02

CPU Cache L共享存储器

2023-08-01 08:10:46

内存缓存

2021-11-18 08:55:49

共享CPU内存

2022-01-17 14:24:09

共享字节面试

2017-08-23 13:21:31

2019-12-10 14:51:00

CPU缓存内存

2022-02-02 21:50:25

底层伪共享CPU

2021-03-01 11:53:15

面试伪共享CPU

2009-04-24 14:46:59

2021-09-16 18:29:17

CPU缓存虚拟

2021-10-29 06:46:42

CPU缓存TLB

2022-08-17 06:25:19

伪共享多线程

2013-06-14 10:12:22

共享并行

2019-04-08 10:09:04

CPU缓存高性能

2025-02-04 10:58:16

2009-06-30 14:08:00

Hibernate缓存

2021-03-29 11:51:07

缓存储存数据
点赞
收藏

51CTO技术栈公众号