文章目录
FalseSharing
CPU缓存架构
- CPU 和主内存间有好几级缓存,
- 直接访问主内存很慢
- 若对一块数据做相同的运算多次,执行运算时把它加载到离 CPU 很近的地方
- 如循环计数,不想每次循环都跑到主内存去取这个数据来增长它吧。
- L1 很小很快,紧靠着在使用它的 CPU 内核。
- L2 大些,慢些,只能被一个单独的核使用。
- L3 在现代多核机器中更普遍,更大,更慢,且被单个插槽上的所有CPU核共享。
- 主存保存着程序运行的所有数据,更大,更慢,
- 由全部插槽上的所有 CPU 核共享。
- CPU运算时,先去L1找,再去L2,然后L3,
- 最后如果这些缓存中都没有,所需的数据就要去主内存拿。
- 走得越远,运算耗时就越长。
- so 若进行很频繁的运算,要确保数据在 L1 缓存。
CPU缓存行
-
缓存由缓存行组成,常64字节(旧的处理器缓存行32)
- 它有效地引用主内存中的一块地址。
-
一个缓存行中可存 8 个 java的long
-
程序运行时,缓存每次更新都从主内存中加载连续64
-
设想有个long型a,是单独的变量,另个long 型变量 b 挨着它,
- 当加载a时将免费加载b。
-
若核的线程在对 a 改,另一个核的线程却对 b 读。
-
当前者改a 时,会把a和b同时加载到前者核心的缓存行中,更新完a后其它所有包含a的缓存行都将失效
- 因为其它缓存中的a不是最新值了。
- 因为其它缓存中的a不是最新值了。
-
这就出现问题,
-
b 和 a 不相干,每次却要因为 a 的更新需要从主内存重新读取,它被缓存未命中给拖慢了。
-
此即传说中的伪共享。
伪共享
- 当多线程修改互相独立的变量时,若这些变量共享同一个缓存行,就会无意中影响彼此的性能,
- 即伪共享。
- 下面例子说明了伪共享是咋肥事。
public class FalseSharingTest {
public static void main(String[] args) throws InterruptedException {
testPointer(new Pointer());
}
private static void testPointer(Pointer pointer) throws InterruptedException {
long start = System.currentTimeMillis();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
pointer.x++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
pointer.y++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(System.currentTimeMillis() - start);
System.out.println(pointer);
}
}
class Pointer {
volatile long x;
volatile long y;
}
- 声明Pointer类,含x和y (必须声明为volatile,保证可见性,关于内存屏障后面再讲),一个线程对 x 自增1亿,一个线程对 y 自增1亿。
- x 和 y 没关系,但更新 x 时会把其它包含 x 的缓存行失效,同时也就失效了 y,运行这段程序输出的时间为3890ms。
避免伪共享
- 一个缓存行64 字节,
- long 8 字节,
- 避免伪共享很简单
在两个 long 类型的变量之间再加 7 个 long 类型
- 把Pointer改成下面
class Pointer {
volatile long x;
long p1, p2, p3, p4, p5, p6, p7;
volatile long y;
}
- 运行程序,时间为695
重新创建自己的 long 类型,而不是 java 自带的 long
- 修改Pointer
class Pointer {
MyLong x = new MyLong();
MyLong y = new MyLong();
}
class MyLong {
volatile long value;
long p1, p2, p3, p4, p5, p6, p7;
}
- pointer.x++; 改pointer.x.value++;,pointer.y++; 改为pointer.y.value++;,
- 间724
使用 @sun.misc.Contended 注解(java8)
- 修改 MyLong 如下:
@sun.misc.Contended
class MyLong {
volatile long value;
}
- 默认使用这个注解是无效的,需要在JVM启动参数加上-XX:-RestrictContended才生效,,
- 再次运行程序718ms。
- 以上三种方式中的前两种是通过加字段的形式实现的,加的字段没有地方使用,
- 可能会被jvm优化掉,建议用第三种。
参考链接
- https://www.jianshu.com/p/7758bb277985
来源:CSDN
作者:fgh431
链接:https://blog.csdn.net/zhoutianzi12/article/details/103244660