网络系统之零拷贝
网络系统之PageCache有什么用
CPU高速缓存
压榨CPU性能
概念
CPU缓存即高速缓冲存储器,是位于CPU与主内存间的一种容量较小但速度很高的存储器。CPU高速缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。
从一级缓存开始往内存开始查找
在CPU访问存储设备时,无论是存取数据抑或存取指令,都趋于聚集在一片连续的区域中,这就是局部性原理。
CPU多核缓存架构缓存一致性
-
有保证的原子操作
- 处理器提供一些特殊的指令或者机制,可以保证在多个处理器同时执行原子操作时,它们不会相互干扰,从而保证原子性。
-
总线锁定,使用LOCK#信号和LOCK指令前缀
-
缓存一致性协议,确保原子操作可以在缓存的数据结构上执行(缓存锁定)
- 缓存锁定则是在缓存一致性协议的基础上实现原子操作的机制,缓存锁定的实现也需要硬件的支持
- 实现原理
- 总线窥探(Bus snooping)是缓存中的一致性控制器(snoopy cache)监视或窥探总线事务的一种方案,其目标是在分布式共享内存系统中维护缓存一致性。
缓存一致性协议不能使用的特殊情况:
-
当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,则处理器会调用总线锁定。
-
有些处理器不支持缓存锁定,只能使用总线锁定来实现原子操作。
伪共享问题
如果多个核上的线程在操作同一个缓存行中的不同变量数据,那么就会出现频繁的缓存失效,即使在代码层面看这两个线程操作的数据之间完全没有关系。这种不合理的资源竞争情况就是伪共享(False Sharing)。
#linux下查看Cache Line大小
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
#查看 cache_alignment 属性
cat /proc/cpuinfo
public class FalseSharingDemo {
public static void main(String[] args) {
try {
testPointer(new Pointer());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
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(pointer.x+","+pointer.y);
System.out.println(System.currentTimeMillis() - start);
}
static class Pointer {
// 避免伪共享: @Contended + jvm参数:-XX:-RestrictContended jdk8支持
// @Contended
//java: 程序包 jdk.internal.vm.annotation 不可见
//(程序包 jdk.internal.vm.annotation 已在模块 java.base 中声明, 但该模块未将它导出到未命名模块)
volatile long x;
//避免伪共享: 缓存行填充
// long p1, p2, p3, p4, p5, p6, p7;
volatile long y;
}
}
高性能内存队列Disruptor
Disruptor通过以下设计来解决队列速度慢的问题
- 环形数组结构
- 为了避免垃圾回收,采用数组而非链表。同时,数组对处理器的缓存机制更加友好(空间局部性原理)。
- 元素位置定位
- 数组长度2^n,通过位运算,加快定位的速度。下标采取递增的形式。不用担心index溢出的问题。index是long类型,即使100万QPS的处理速度,也需要30万年才能用完。
- 无锁设计
- 每个生产者或者消费者线程,会通过先申请可以操作的元素在数组中的位置,申请到之后,直接在该位置写入或者读取数据。整个过程通过原子变量CAS,保证操作的线程安全。
- 利用缓存行填充解决了伪共享的问题
- 实现了基于事件驱动的生产者消费者模型(观察者模式)
- 消费者时刻关注着队列里有没有消息,一旦有新消息产生,消费者线程就会立刻把它消费