动手测试: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这么大的重要原因。

相关推荐
编程卡拉米2 小时前
redis的数据结构,内存处理,缓存问题
数据库·redis·缓存
Code apprenticeship2 小时前
腾讯一面-LRU缓存
缓存
胡耀超4 小时前
缓存是什么?缓存机制、Spring缓存管理、Redis数据一致性、缓存问题(缓存穿透、缓存雪崩、缓存击穿)及Redis与MySQL使用场景对比
redis·spring·缓存
wclass-zhengge4 小时前
Redis篇(缓存机制 - 多级缓存)(持续更新迭代)
数据库·redis·缓存
LyaJpunov4 小时前
使用双向链表和哈希表实现LRU缓存
链表·缓存·散列表
big_noob11 小时前
centos7安装Redis单机版
数据库·redis·缓存·redis安装·redis安装教程·redis安装步骤·centos安装redis
Java中的战斗机18 小时前
redis缓存工具类(Java)
java·redis·缓存
小李超勇的..21 小时前
Redis——缓存
数据库·redis·缓存
前端李易安21 小时前
javaScript中如何实现函数缓存,案例解析
开发语言·javascript·缓存