CPU cache
目前的CPU都是有三级缓存,一级二级是每个CPU独有的,三级缓存是所有CPU共有的,其中L1的缓存分为数据缓存和指令缓存,如下图所示:
虚拟化: VT-x
L1d 缓存: 128 KiB // L1数据缓存
L1i 缓存: 128 KiB // L1 指令缓存
L2 缓存: 1 MiB
L3 缓存: 8 MiB
NUMA 节点0 CPU: 0-7
程序的数据最好能放到L1里面,大数据要进行拆分
缓存命中测试,保证测试的数据量大于L1+L2,系统会将内存放到L3缓存中,因此如下代码测试时输入的可以为 0K --> 8000 保证内存从L1 到L3的存取过程都能看到
//
// Created by andrew on 2021/9/20.
//
#include <iostream>
#include <vector>
#include <sched.h>
#include <ctime>
#include <fstream>
using namespace std;
#define CACHE_LINE_SIZE 64
struct Node {
Node *next;
char paddings[CACHE_LINE_SIZE - sizeof(void *)];
};
// 内存扫描,使用链表的方式每次调用指针去踩内存
#define N1(node) node=node->next;
#define N2(node) N1(node);N1(node);
#define N4(node) N2(node);N2(node);
#define N8(node) N4(node);N4(node);
#define N16(node) N8(node);N8(node);
#define N32(node) N16(node);N16(node);
#define N64(node) N32(node);N32(node);
#define N128(node) N64(node);N64(node);
#define N256(node) N128(node);N128(node);
#define N512(node) N256(node);N256(node);
#define N1024(node) N512(node);N512(node);
const long long sec2non = 1000000000;
const Node *Test(int T, const Node *c, std::size_t size, ofstream & outFile) {
// 提前定义,防止在时间打点的时候将变量申请的时间计算在内
struct timespec start{};
struct timespec end{};
const Node* node = nullptr;
//< start
clock_gettime(CLOCK_MONOTONIC, &start);
// 保证测试的次数,也就是踩内存的次数和T一样
const int M = static_cast<int>(T / size);
for (int i = 0; i < M; i ++) {
node = c;
const int s = static_cast<int>(size / 64);
for (int j = 0; j < s; ++ j) {
// 踩缓存
N64(node);
}
}
clock_gettime(CLOCK_MONOTONIC, &end);
//< end
// 计算总耗时
auto elapsed = (end.tv_sec - start.tv_sec) * sec2non + end.tv_nsec - start.tv_nsec;
// 总耗时 除缓存命中次数,计算缓存命中耗时
std::cout << "elapsed milliseconds per elements : " << elapsed / M << "ns" << std::endl;
outFile << elapsed / M << endl;
return node;
}
// 代码地址 https://github.com/zzu-andrew/optimized_cplusplus.git
/*
* @brief 系统缓存信息
* L1d 缓存: 128 KiB
* L1i 缓存: 128 KiB
* L2 缓存: 1 MiB
* L3 缓存: 8 MiB
* */
//< rdmsr: version msr-tools-1.3 rdmsr 版本信息
//< rdmsr - tool for reading CPU machine specific registers (MSR)
int main(int argc, char*argv[]) {
if (argc < 3) {
std::cout << "argc : " << argc << std::endl;
return -1;
}
// 清空CPU 参数,避免影响缓存命中
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(0, &set);
sched_setaffinity(0, sizeof(set), &set);
// 16M,保证内存数量大于一二级缓存的量,才能保证系统会将缓存放到三级缓存里面
// 如果一级 二级缓存够使用 系统将不会将内存放到三级缓存里面
const int N = 1024 * 1024 * 16; // * (64)
// 使用vector搞一个环形buffer
std::vector<Node> va;
va.reserve(N);
for (int i = 0; i < N - 1; i ++) {
va[i].next = &(va[i + 1]);
}
va[N - 1].next = &va[0];
const int M = 1000000;
char *endPtr = nullptr;
// 转换成10进制数
const int beginKb = static_cast<int>(strtol(argv[1], &endPtr, 10));
const int endKb = static_cast<int>(strtol(argv[2], &endPtr, 10));
const Node* node = nullptr;
// 耗时统计
// 耗时统计
ofstream outfile("./ou.csv", ios::trunc);
for (int i = beginKb; i < endKb; i += 4) {
std::cout << "test Kb : " << i << std::endl;
std::cerr << i << '\t';
node = Test(M, &va[0], i * 1024 / sizeof(Node), outfile);
}
outfile.close();
std::cout << node << '\t' << &va[0] << std::endl;
return 0;
}