动手测试:CPU的L1~L3级缓存和内存的读取速度测试

引言

在许多文章中指出了这些缓存的架构,速度差异等。纸上得来终觉浅,今天想实际写代码简单测试一下。

背景

现代计算机系统中,CPU缓存(L1、L2、L3)和主内存(RAM)之间的读取速度有着显著的差异。缓存的主要目的是提高数据访问的速度,从而提升整体的系统性能。本篇文章将通过一系列的测试来探索不同大小内存块的读写性能,从而揭示缓存和内存之间读取速度的变化规律。

测试思路

我们通过改变内存块的大小来观察缓存命中和未命中的情况。具体而言,我们会从小块内存(128字节)开始测试,逐渐增加到较大的内存块(10GB),以此来观察性能曲线的变化。

测试代码

以下是用于测试的C++代码:

cpp 复制代码
#include <iostream>
#include <vector>
#include <chrono>
#include <cstdlib>

// 记录读写操作的时间
void testMemoryAccessSpeed(size_t blockSize) {
	size_t totalBytes = 1024ull * 1024 * 1024 * 100; // 总共读写100GB的数据
	size_t iterations = totalBytes / blockSize;
	std::vector<size_t> buffer(blockSize / sizeof(size_t));
	// 初始化计时器
	auto start = std::chrono::high_resolution_clock::now();
	// 执行读写操作
	for (size_t i = 0; i < iterations; ++i) {
		memset(buffer.data(), 551546, buffer.size() * sizeof(size_t));
	}
	// 记录结束时间
	auto end = std::chrono::high_resolution_clock::now();
	auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

	// 输出结果
	if (blockSize > 1024 * 1024) {
		std::cout << "Block Size (MB): " << blockSize / 1024 / 1024 << ", Time (ms): " << duration.count() << " Loops:"<< iterations << std::endl;
	}
	else if (blockSize > 1024) {
		std::cout << "Block Size (KB): " << blockSize / 1024 << ", Time (ms): " << duration.count() << " Loops:" << iterations << std::endl;
	}
	else {
		std::cout << "Block Size ( B): " << blockSize << ", Time (ms): " << duration.count() << " Loops:" << iterations << std::endl;
	}
}

int main() {
	// 测试不同大小的内存块
	std::vector<size_t> blockSizeVec = {
		128, 512, 1024, 2 * 1024, //128B ~ 2KB
		4 * 1024, 64 * 1024, 512 * 1024, 4 * 1024 * 1024,  // 4KB ~ 4 MB
		16 * 1024 * 1024, 64 * 1024 * 1024, 256 * 1024 * 1024, 512 * 1024 * 1024,  // 16M ~ 512M
		1024 * 1024 * 1024, 2ull * 1024 * 1024 * 1024, 5ull * 1024 * 1024 * 1024, 10ull * 1024 * 1024 * 1024  // 1G ~ 10G
	};
	const size_t maxBlockSize = 128 * 1024 * 1024; // 最大到128MB

	for (auto it : blockSizeVec) {
		testMemoryAccessSpeed(it);
	}

	return 0;
}

测试的硬件平台为:


测试结果

以下是测试代码的输出:

结果粗略评估和解释

  1. 超小块内存(小于1KB)

    • 当内存块较小(如128字节)时,需要执行的循环次数成倍增加,整体的耗时更多在循环指令和函数调用(memset)上,而非数据读取。
    • 随着内存块大小的增加,循环次数减少,内存读写开始成为瓶颈,数据开始有意义。
  2. 小块内存(1KB至4KB)

    • 当内存块较小时,读写操作主要发生在L1缓存中,因此时间较长。
    • 随着内存块大小的增加,L1缓存的利用率下降,但L2缓存开始发挥作用,因此时间逐渐增加。
  3. 中等大小内存块(4KB至4MB)

    • 在这个范围内,L2缓存成为主要存储介质,性能较高。
    • 当内存块大小增加到一定程度,L2缓存开始饱和,性能下降。
  4. 大块内存(4MB至30MB)

    • L3缓存成为主要存储介质,性能再次提高。
    • 当内存块进一步增加到几十MB时,L3缓存也逐渐饱和,性能趋于稳定。
  5. 超大块内存(30MB及以上)

    • 超过L3缓存的容量后,性能受内存读取速度的限制,因此时间较为稳定。

excel 作图分析

在上图中,红色参考线是根据CPU-Z给出的单核心最大的L1~L3的缓存大小参考。注意,横坐标的BlockSize的增长不是线性增长的,但纵坐标轴Time是线性的。

从上图可以根据Time来粗略评估L1~L3再到内存的性能,我们选取了这几个样本点作为各个缓存性能的代表:

我的测试结论

根据选取的样本点,即可得出不同存储类型的性能参考:

上面的L1数据不是特别严谨,因为在块较小时,在循环和函数调用上会有额外的耗时,因此L1的性能应该比测试数据更高。具体高多少,可以根据前面的超小块内存测试结果进行评估。

网上其他相关测试的结论


总结

综合网上的资料和我的实践结论,基本可以得出如下概念:

  1. L1的速度是L2的2~5倍 ,我的测试结果是3.8倍
  2. L2的速度是L3的2~3倍 ,我的测试结果是1.8倍
  3. L3的速度是内存的3~7倍 ,我的测试结果是2.4倍(DDR5@4000M)。

这里的性能测试结果仅仅是上面的代码的运行耗时结果,不代表在大部分场景下L3的性能比内存高3~7倍。CPU访问内存需要经过复杂的内存控制器,主板的内存总线,再到内存控制器等复杂路径,每次读写也要操作一系列内存寄存器,从程序的角度来看,读写内存的延时要远远大于L3。从上图AIDA的测试结果中也能看出,内存的延时是L3的十倍。

对于实际程序运行的情况,内存延时带来的耗时增加是更严重的,这也是现代CPU把L3缓存做到30MB这么大的重要原因。

相关推荐
wang09078 小时前
netty之实现一个redis的客户端
数据库·redis·缓存
LeonNo118 小时前
【软考】Redis不同的数据类型和应用场景。
数据库·redis·缓存
Karoku0669 小时前
【缓存与加速技术实践】Web缓存代理与CDN内容分发网络
运维·数据库·redis·mysql·nginx·缓存
w_t_y_y9 小时前
Mybatis中的缓存
java·缓存·mybatis
Karoku06613 小时前
【缓存与加速技术实践】Redis哨兵
linux·运维·服务器·数据库·redis·mysql·缓存
微刻时光14 小时前
Windows上安装Redis
运维·数据库·windows·redis·缓存
码上一元16 小时前
深入解析缓存模式下的数据一致性问题
缓存·数据一致性
PGCCC18 小时前
【PGCCC】Postgresql 缓存池原理
spring·缓存·postgresql
TT编程19 小时前
数据库Redis篇
数据库·redis·mysql·缓存·面试
binbinxyz20 小时前
Redis 组网方式入门
数据库·redis·缓存