共计 2394 个字符,预计需要花费 6 分钟才能阅读完成。
在《内存随机也比程序拜访慢,带你深刻了解内存 IO 过程》一文中,咱们了解了内存 IO 的外部实现过程,晓得了内存的随机 IO 比程序 IO 要慢,并对延迟时间进行了大略的估算。那么咱们明天来用代码的形式来工夫一下,看看在咱们的我的项目工程中,内存拜访的在不同的拜访场景下延时到底是个什么体现。
先测程序状况
测试原理就是定义一个指定大小的 double(8 字节)数组,而后以指定的步长去循环。这外面的变量有两个。外围代码如下:
void init_data(double *data, int n){
int i;
for (i = 0; i < n; i++) {data[i] = i;
}
}
void seque_access(int elems, int stride) {
int i;
double result = 0.0;
volatile double sink;
for (i = 0; i < elems; i += stride) {result += data[i];
}
sink = result;
}
在这个外围代码的根底上,咱们有两个可调节变量:
- 一是数组大小,数组越小,高速缓存命中率越高,均匀延时就会越低。
- 二是循环步长,步长越小,程序性越好,同样也会减少缓存命中率,均匀延时也低。
咱们再测试的过程中采取的方法是,固定其中一个变量,而后动静调节另外一个变量来查看成果。
另外阐明一下,这个代码测试中思考的几个额定的开销的解决状况。
1. 加法开销:因为加法指令简略,一个 CPU 周期就可实现,CPU 周期比内存周期要快,所以暂且疏忽它。
2. 耗时统计:这波及到高开销的零碎调用,本试验通过跑 1000 次取一次耗时的形式来升高影响。
场景一:固定数组大小 2K,调节步长
步长 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 |
---|---|---|---|---|---|---|---|---|
延时 ns | 1.28 | 1.28 | 1.33 | 1.30 | 1.30 | 1.41 | 1.45 | 1.4 |
数组足够小的时候,L1 cache 全副都能装的下。内存 IO 产生较少,大部分都是高效的缓存 IO,所以我这里看到的内存延时只有 1ns 左右,这其实只是虚拟地址转换 +L1 拜访的延时。
场景二:固定步长为 8,数组从 32K 到 64M
数组大小 | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
---|---|---|---|---|---|---|---|---|
延时 ns | 1.27 | 1.73 | 2.03 | 2.62 | 2.62 | 2.88 | 5.17 | 5.84 |
当数组越来越大,Cache 装不下,导致穿透高速缓存,到内存理论 IO 的次数就会变多,均匀耗时就减少
场景三:步长为 32,数组从 32K 到 64M
数组大小 | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
---|---|---|---|---|---|---|---|---|
延时 ns | 1.25 | 1.74 | 2.03 | 2.47 | 2.47 | 3.29 | 7.73 | 8.89 |
和场景二相比,步长变大当前,局部性变差,穿透的内存 IO 进一步减少。尽管数据量一样大,然而均匀耗时就会持续有所上涨。不过尽管穿透减少,但因为拜访地址依然绝对比拟间断,所以即便产生内存 IO 也绝大部分都是行地址不变的程序 IO 状况。所以耗时在 9ns 左右,和之前估算大抵相符!
另外留神一个细节,就是随着数组从 64M 到 32M 变动的过程中。耗时有几个显著的降落点,别离是 8M,256K 和 32K。这是因为本机的 CPU 的 L1 大小是 32K,L2 是 256K,L3 是 12M。在数据集 32K 的时候,L1 全能装的下,所有根本都是高速缓存 IO。256K 的时候、8M 的时候,尽管 L1 命中率降落,然而 L2、L3 访问速度依然比真正的内存 IO 快。然而超过 12M 当前越多,真正的内存 IO 就越来越多了。
再测随机 IO 状况
在程序的试验场景里,数组的下标拜访都是比拟有法则地递增。在随机 IO 的测试中,咱们要彻底打乱这个法则,提前随机好一个下标数组,试验时不停地拜访数组的各个随机地位。
void init_data(double *data, int n){
int i;
for (i = 0; i < n; i++) {data[i] = i;
}
}
void random_access(int* random_index_arr, int count) {
int i;
double result = 0.0;
volatile double sink;
for (i = 0; i < count; i++) {result += data[*(random_index_arr+i)];
}
sink = result;
}
这理论比下面的试验多了一次内存 IO,但因为对 random\_index\_arr 的拜访时程序的,而且该数组也比拟小。咱们假如它全副能命中高速缓存,所以暂且疏忽它的影响。
随机试验场景:数组从 32K 到 64M
数组大小 | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
---|---|---|---|---|---|---|---|---|
延时 ns | 2.4 | 2.4 | 2.4 | 4.8 | 4.8 | 19.2 | 24 | 38.4 |
这次的数组拜访就没有步长的概念了,全副打乱,随机拜访。当数据集比拟小的时候、L1、L2、L3 还能抗一抗。但当减少到 16M、64M 当前,穿透到内存的 IO 状况会变多,穿透过来当前极大可能行地址也会变。在 64M 的数据集中,内存的延时居然降落到了 38.4ns,和咱们估算的也基本一致。
论断
有了试验数据的佐证,进一步证实了上一文《深刻了解内存 IO 的理论过程!》的论断。 内存也存在随机拜访比程序拜访慢的多的状况,大略是 4:1 的关系。所以不要感觉内存很快,就用起来太随性了!
开发内功修炼之内存篇专辑:
- 1. 带你深刻了解内存对齐最底层原理
- 2. 内存随机也比程序拜访慢,带你深刻了解内存 IO 过程
- 3. 从 DDR 到 DDR4,内存外围频率其实基本上就没太大的提高
- 4. 理论测试内存在程序 IO 和随机 IO 时的拜访延时差别
- 5. 揭穿内存厂家“谎话”,实测内存带宽实在体现
- 6.NUMA 架构下的内存拜访提早区别!
- 7.PHP7 内存性能优化的思维精华
- 8. 一次内存性能晋升的我的项目实际
- 9. 挑战 Redis 单实例内存最大极限,“遭逢”NUMA 陷阱!
我的公众号是「开发内功修炼」,在这里我不是单纯介绍技术实践,也不只介绍实践经验。而是把实践与实际联合起来,用实际加深对实践的了解、用实践进步你的技术实际能力。欢送你来关注我的公众号,也请分享给你的好友~~~