带宽是存储系统一个的一个非常重要的衡量指标,内存带宽指的是CPU从内存读取或写入数据到内存的速率。我们今天来思考一个问题,在你的应用程序里内存的访问带宽能够达到多少?
各代内存带宽规格
理论上内存带宽的计算公式是:Band Width = Speed * Data Width
。在这个公式的基础上,我们来看一下各代规格的内存带宽表现。
从上图来看,DDR3代的内存 DDR3代1066MHz的带宽都已经达到了8.5GB/s, DDR4代3200MHz更是达到了惊人的25.6G。 厂家在进行销售的时候也都是用这个公式来算,言外之意就是告诉你:“我家的产品非常快,大家赶快来买啊!”
厂家这么宣传似乎也不算错,但是有诱导性的谎言在其中。 厂家这个数据的基础是内存严格以顺序IO的方式工作,而且把传输的内存地址也当数据来看,进而计算出的结果。但我们在《内存随机访问也比顺序慢,带你深入理解内存IO过程》中明白了的是,内存在并一定是所有的Data Speed周期都在源源不断往外吐数据。在CPU传入了行地址后,内存打开一行需要tRCD+tRP个时钟周期的延迟。输入列地址后,又需要等待CL个时钟周期。而内存作为易失性存储元器件,又需要定时对所有的行进行充电,来保证数据不丢失。所以,在实践中,我们并不是总是能够达到厂家宣传的理论带宽值。
>注意:理论带宽值计算时采用的是内存的Speed,也就是其数据频率。而内存的延迟呢,用的是时钟周期。现代的内存在时钟周期的上沿和下沿都可以传输数据,所以数据频率比Speed又慢了一半。对于笔者Speed为1067MHz的内存条,其时钟频率是553MHz。
邓爷爷说过,实践是检验真理的唯一标准。我们今天就来进行一下实际的测试,看看内存的到底每秒能给我们吐出多少的数据。和前文《实际测试内存在顺序IO和随机IO时的访问延时差异》的测试方法类似,我们今天对方法进行下小改动,用它来测试带宽。
顺序IO情况核心测试方法
测试代码主体上和延迟测试差不多,定义一个指定大小的数组,然后以指定步长对其进行访问。
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;
}
考虑到内存对齐能提高性能,所以公平起见,我们每次都是按内存位宽去取的(一个double 8个字节,正好是一个内存位宽)。带宽就是一秒内访问过的字节总数,所以我们通过如下代码进行计算。
result = total_accessed_bytes * 1000 / used_microseconds;
顺序IO情况带宽测试结果
笔者的服务器上的内存条是DDR3,1067MHz,延迟参数为7-7-7-24。我们进行了多场景的测试。
场景一: 固定数组大小2K,调节步长
步长 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 | 64 |
---|---|---|---|---|---|---|---|---|---|
带宽MB | 6185 | 6263 | 6001 | 5930 | 6116 | 5660 | 5523 | 5682 | 6316 |
数组为2k,足够小到L1 cache全部都能装的下。这时候其实基本实际内存IO发生的很少,大部分都是更高效的L1 cache的IO,在CPU内部就完成了。但最高值也才6G而已,也没有达到厂家宣称的8GB。
场景二: 固定步长为8,数组从32K到64M
数组大小 | 8K | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
---|---|---|---|---|---|---|---|---|---|
带宽MB | 6066 | 6314 | 4577 | 3960 | 3059 | 3047 | 2769 | 1551 | 1386 |
数组越大,Cache越装不下,数据访问的IO会更多地往后穿透到L2、L3和内存。L1、L2、L3和内存IO的性能依次递减,因此数组越大,平均带宽就会越低。
场景三: 步长为32,数组从32K到64M
数组大小 | 8K | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
---|---|---|---|---|---|---|---|---|---|
带宽MB | 6376 | 6394 | 4647 | 3735 | 3193 | 3179 | 1481 | 1002 | 902 |
步长增加后,穿透到内存的次数进一步增加,带宽进一步下降。这个时候,我们应用程序视角看到的数据带宽已经下降到1GB以下了。
再测随机IO情况
前面的测试情况,虽然步长也在变化,但都是有序递增。这样内存的连续两次IO之间,虽然列地址会变,但是行地址极有可能不发生变化,因此效率还是算比较高。 我们这次是来彻底随机进行访问,再来看一下。
** 随机访问,数组从8K到64M **
数组大小 | 8K | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
---|---|---|---|---|---|---|---|---|---|
带宽MB | 6378 | 5850 | 4764 | 3405 | 3193 | 2948 | 967 | 749 | 474 |
当数组比较小的时候,虽然乱序访问,但是cache能兜住,因此内存IO实际上发生的很少。但当数组增加到64M的时候,再加上哪一级Cache都兜不住了。再加上访问又足够随机,因此都穿透到了内存,而且行的行地址也极大可能发生变化。这时,内存带宽竟然下降到了474M。
结论
厂家在宣传的内存带宽数据的时候,用的是严格顺序IO情况下的数据,而且把地址的传输也当数据算了。但是厂家不会告诉你,在实际中内存的带宽并没有你想象的那么快,在随机IO工作模式的情况下,带宽只有474M而已。 现在SSD固态硬盘顺序IO也差不多能达到这个数量级了。所以,我们以后不要鲁莽地说内存比硬盘要快很多。快不快,关键取决于你怎么用!
在这里我不是单纯介绍技术理论,也不只介绍实践经验。而是把理论与实践结合起来,用实践加深对理论的理解、用理论提高你的技术实践能力。欢迎你来关注我的公众号,也请分享给你的好友~~~