C++面试基础:如何高效利用CPU缓存

  

目录

局部性原理(Locality Principle)

数据结构的布局

缓存友好的算法

缓存大小和关联性

避免随机访问

使用缓存友好的数据结构

总结


      高效利用CPU缓存是编写高性能C++代码的关键之一。以下是一些在面试中可能会讨论到的方法。

局部性原理(Locality Principle)

  • 时间局部性(Time Locality):利用最近使用的数据很可能会在不久的将来再次被使用。这意味着如果你在循环中使用了某个数据,它很可能会被缓存在CPU缓存中,从而提高访问速度。
  • 空间局部性(Spatial Locality):在处理连续内存块时,相邻的内存单元很可能会被一起缓存。因此,访问相邻内存单元的数据可以充分利用CPU缓存。
#include <iostream>
#include <vector>int main() {// 创建一个大小为10000的整数向量std::vector<int> vec(10000);// 初始化向量for (int i = 0; i < 10000; ++i) {vec[i] = i;}// 计算向量中所有元素的和int sum = 0;for (int i = 0; i < 10000; ++i) {// 利用时间局部性:sum变量在循环中被反复使用,因此可能会被缓存在CPU缓存中sum += vec[i];}std::cout << "Sum: " << sum << std::endl;return 0;
}

细解析和注释

  1. 在这个示例中,我们首先创建了一个大小为10000的整数向量 vec,它会占据一块连续的内存空间。这符合空间局部性原则,相邻的内存单元很可能会被一起缓存。

  2. 然后,我们初始化向量中的每个元素,将其设置为与索引相等的值。这个过程并不涉及任何复杂的内存访问模式,因此利用了时间局部性原则,初始化过的数据可能会被缓存在CPU缓存中。

  3. 接下来,我们计算向量中所有元素的和。在循环中,我们对 sum 变量进行反复的累加操作。由于 sum 变量在循环中被频繁使用,它可能会被缓存在CPU缓存中,从而利用了时间局部性原则。

  4. 最后,我们输出计算得到的总和。

        通过利用时间局部性和空间局部性原则,这段代码可以更高效地利用CPU缓存,提高访问速度。

 

#include <iostream>
#include <vector>const int N = 1000;// 矩阵相乘函数
void matrixMultiplication(const std::vector<std::vector<int>>& matrixA,const std::vector<std::vector<int>>& matrixB,std::vector<std::vector<int>>& result) {for (int i = 0; i < N; ++i) {for (int j = 0; j < N; ++j) {// 利用时间局部性:result[i][j] 在循环中被频繁使用int sum = 0;for (int k = 0; k < N; ++k) {// 利用空间局部性:matrixA[i][k] 和 matrixB[k][j] 可能会被缓存在CPU缓存中sum += matrixA[i][k] * matrixB[k][j];}result[i][j] = sum;}}
}int main() {// 创建并初始化矩阵std::vector<std::vector<int>> matrixA(N, std::vector<int>(N, 1));std::vector<std::vector<int>> matrixB(N, std::vector<int>(N, 2));std::vector<std::vector<int>> result(N, std::vector<int>(N));// 计算矩阵相乘matrixMultiplication(matrixA, matrixB, result);// 输出结果std::cout << "Result:" << std::endl;for (int i = 0; i < N; ++i) {for (int j = 0; j < N; ++j) {std::cout << result[i][j] << " ";}std::cout << std::endl;}return 0;
}

        这个例子中,我们计算了两个大小为1000x1000的矩阵的乘积。在相乘的过程中,我们通过嵌套的三重循环遍历了矩阵元素。在最内层的循环中,我们对 matrixA[i][k]matrixB[k][j] 进行访问,利用了空间局部性。而在最外层的循环中,我们对 result[i][j] 进行更新,利用了时间局部性。

数据结构的布局

  • 优化数据结构的布局以最大程度地利用CPU缓存。例如,将紧密相关的数据放置在相邻的内存位置,以提高局部性。
  • 避免不必要的内存碎片,以确保数据在内存中的连续性。
#include <iostream>
#include <vector>// 定义一个结构体表示学生信息
struct Student {int id;char name[20];int age;
};int main() {const int numStudents = 1000;std::vector<Student> students(numStudents);// 初始化学生信息for (int i = 0; i < numStudents; ++i) {students[i].id = i + 1;sprintf(students[i].name, "Student%d", i + 1);students[i].age = 20 + i % 5;}// 计算所有学生的平均年龄int totalAge = 0;for (int i = 0; i < numStudents; ++i) {// 利用局部性原理:紧密相关的数据(id, name, age)被连续地存储在内存中totalAge += students[i].age;}double averageAge = static_cast<double>(totalAge) / numStudents;std::cout << "Average Age: " << averageAge << std::endl;return 0;
}

详细解析和注释

  1. 在这个示例中,我们定义了一个 Student 结构体,表示学生的基本信息,包括学生ID、姓名和年龄。

  2. 我们创建了一个大小为1000的 std::vector<Student> 容器,其中存储了1000个学生的信息。在内存中,这些 Student 结构体对象是连续存储的,这样就充分利用了空间局部性原理。

  3. 我们通过循环初始化了每个学生的信息,这里 sprintf 函数用于将学生姓名格式化为 "Student1""Student2" 这样的字符串,以便于区分。

  4. 在计算所有学生的平均年龄时,我们再次利用了局部性原理。在循环中,我们依次访问每个学生对象的 age 成员,由于紧密相关的数据被连续地存储在内存中,因此这些访问操作可以更有效地利用CPU缓存。

 

缓存友好的算法

  • 选择算法时要考虑其对CPU缓存的利用程度。例如,遍历数组时,尽量保证对数组元素的访问是连续的,以利用空间局部性。
  • 考虑使用分治法或动态规划等算法来减少缓存未命中的次数。
#include <iostream>
#include <vector>// 使用动态规划计算斐波那契数列的第n项
int fibonacci(int n) {std::vector<int> fib(n + 1);// 初始化前两个斐波那契数fib[0] = 0;fib[1] = 1;// 计算斐波那契数列的每一项for (int i = 2; i <= n; ++i) {// 利用空间局部性:fib[i-1] 和 fib[i-2] 可能会被缓存在CPU缓存中fib[i] = fib[i - 1] + fib[i - 2];}return fib[n];
}int main() {int n = 10;int result = fibonacci(n);std::cout << "Fibonacci(" << n << ") = " << result << std::endl;return 0;
}

详细解析和注释

  1. 在这个示例中,我们使用动态规划算法计算斐波那契数列的第n项。

  2. 我们定义了一个 fib 向量,用于存储计算过程中的中间结果。在循环中,我们会逐步填充这个向量。

  3. 在循环中,我们每次计算 fib[i] 时,都需要使用 fib[i-1]fib[i-2] 的值。由于这些值在内存中相邻且紧密相关,因此它们有很大的可能性被缓存在CPU缓存中,利用了空间局部性。

  4. 通过使用动态规划算法,我们可以有效地减少缓存未命中的次数,因为我们只需要一次遍历来填充 fib 向量,而不需要重复计算已经得到的中间结果。

缓存大小和关联性

  • 了解目标CPU的缓存大小和关联性,以更好地优化代码。不同的CPU可能具有不同大小和类型的缓存,因此需要针对特定的硬件进行优化。
#include <iostream>
#include <vector>
#include <chrono>const int N = 1000; // 矩阵维度// 矩阵相乘函数,使用分块优化
void matrixMultiplication(const std::vector<std::vector<int>>& matrixA,const std::vector<std::vector<int>>& matrixB,std::vector<std::vector<int>>& result) {const int blockSize = 32; // 分块大小,根据CPU缓存大小和关联性调整for (int i = 0; i < N; i += blockSize) {for (int j = 0; j < N; j += blockSize) {for (int k = 0; k < N; k += blockSize) {// 分块计算for (int ii = i; ii < std::min(i + blockSize, N); ++ii) {for (int jj = j; jj < std::min(j + blockSize, N); ++jj) {int sum = 0;for (int kk = k; kk < std::min(k + blockSize, N); ++kk) {sum += matrixA[ii][kk] * matrixB[kk][jj];}result[ii][jj] += sum;}}}}}
}int main() {std::vector<std::vector<int>> matrixA(N, std::vector<int>(N, 1)); // 初始化矩阵Astd::vector<std::vector<int>> matrixB(N, std::vector<int>(N, 2)); // 初始化矩阵Bstd::vector<std::vector<int>> result(N, std::vector<int>(N, 0)); // 结果矩阵auto start = std::chrono::steady_clock::now();// 计算矩阵相乘matrixMultiplication(matrixA, matrixB, result);auto end = std::chrono::steady_clock::now();std::chrono::duration<double> elapsed_seconds = end - start;std::cout << "Time taken: " << elapsed_seconds.count() << "s" << std::endl;return 0;
}

详细解析和注释

  1. matrixMultiplication 函数中,我们使用了分块的方法来优化矩阵相乘过程。分块的大小 blockSize 是根据目标CPU的缓存大小和关联性进行调整的,以尽可能利用CPU缓存。

  2. 在三重嵌套的循环中,我们将矩阵相乘的过程分成了若干个小块,每个小块的大小由 blockSize 决定。这样做有助于利用CPU缓存的空间局部性,因为每次计算都集中在一个小块中,避免了频繁地访问非相邻的内存单元。

  3. 在循环中,我们使用 std::min 函数来确保我们不会超出矩阵的边界,这样可以避免对不存在的数据进行访问,提高了代码的健壮性。

 

避免随机访问

  • 尽量避免在内存中进行随机访问,因为这可能导致缓存未命中。如果难以避免,可以尝试通过重新组织数据或使用缓存友好的数据结构来减少随机访问的影响。

        我们将展示一个遍历二维数组的例子,并说明如何使用行优先存储顺序来提高缓存命中率。代码中包含详细的解析和注释。

#include <iostream>
#include <vector>const int ROWS = 1000;
const int COLS = 1000;// 使用行优先存储顺序的二维数组遍历函数
void traverseArray(std::vector<std::vector<int>>& array) {int sum = 0;// 外层循环遍历行for (int i = 0; i < ROWS; ++i) {// 内层循环遍历列for (int j = 0; j < COLS; ++j) {// 利用局部性原理:按行连续访问数组元素,提高缓存命中率sum += array[i][j];}}std::cout << "Sum: " << sum << std::endl;
}int main() {// 创建一个二维数组并初始化std::vector<std::vector<int>> array(ROWS, std::vector<int>(COLS));for (int i = 0; i < ROWS; ++i) {for (int j = 0; j < COLS; ++j) {array[i][j] = i * COLS + j;}}// 调用遍历数组的函数traverseArray(array);return 0;
}

详细解析和注释

  1. 在这个示例中,我们定义了一个二维数组 array,其大小为 ROWSCOLS 列,并初始化了每个元素的值。

  2. 我们编写了一个名为 traverseArray 的函数,用于遍历二维数组并计算元素的总和。

  3. 在遍历数组的过程中,我们使用了行优先存储顺序。即外层循环遍历行,内层循环遍历列。这样做有助于提高缓存命中率,因为在内存中按行连续访问数组元素,充分利用了空间局部性原理。

 

使用缓存友好的数据结构

  • 例如,使用数组而不是链表可以提高空间局部性,因为数组的元素在内存中是连续存储的。
#include <iostream>
#include <vector>// 基于数组的栈实现
class ArrayStack {
private:std::vector<int> data; // 使用数组存储栈元素
public:// 入栈操作void push(int val) {data.push_back(val); // 将元素添加到数组末尾}// 出栈操作int pop() {if (data.empty()) {std::cerr << "Error: Stack is empty!" << std::endl;return -1; // 出错时返回-1}int topVal = data.back(); // 获取栈顶元素data.pop_back(); // 删除栈顶元素return topVal;}// 判断栈是否为空bool isEmpty() {return data.empty();}
};int main() {// 创建基于数组的栈对象ArrayStack stack;// 入栈操作stack.push(10);stack.push(20);stack.push(30);// 出栈操作std::cout << stack.pop() << std::endl; // 应输出30std::cout << stack.pop() << std::endl; // 应输出20std::cout << stack.pop() << std::endl; // 应输出10// 尝试从空栈中弹出元素std::cout << stack.pop() << std::endl; // 应输出错误信息return 0;
}

详细注释解析

  1. 在这个示例中,我们实现了一个基于数组的栈数据结构 ArrayStack。栈是一种后进先出(LIFO)的数据结构,所以我们使用 vector 来存储栈元素,因为 vector 支持在末尾进行快速的插入和删除操作。

  2. push 方法用于将元素压入栈中,它通过调用 vectorpush_back 方法将元素添加到数组的末尾。

  3. pop 方法用于从栈中弹出元素,它首先检查栈是否为空,然后从数组的末尾删除元素并返回栈顶元素。

  4. isEmpty 方法用于判断栈是否为空,它简单地调用 vectorempty 方法。

总结

        高效利用CPU缓存是优化代码以提高性能的重要方面。以下是一些关键点总结:

  1. 局部性原理

    • 时间局部性:利用最近使用的数据很可能会在不久的将来再次被使用。因此,频繁访问相同的数据可以提高缓存命中率。
    • 空间局部性:在处理连续内存块时,相邻的内存单元很可能会被一起缓存。因此,访问相邻内存单元的数据可以充分利用CPU缓存。
  2. 数据结构的布局

    • 优化数据结构的布局以最大程度地利用CPU缓存。例如,将紧密相关的数据放置在相邻的内存位置,使用数组而不是链表可以提高空间局部性。
  3. 缓存友好的算法

    • 选择算法时要考虑其对CPU缓存的利用程度。例如,避免随机访问,尽量保证对数据的访问是连续的,使用分治法或动态规划等算法来减少缓存未命中的次数。
  4. 了解目标CPU的缓存大小和关联性

    • 不同的CPU可能具有不同大小和类型的缓存,了解目标CPU的缓存特性可以更好地优化代码。
  5. 避免假共享

    • 当多个线程在不同的CPU核心上访问同一缓存行的不同部分时可能会发生假共享,这会降低性能。通过调整数据结构的布局或使用填充技术来减少假共享。
  6. 使用缓存友好的数据结构和布局

    • 避免过多的随机访问,尽量保证数据的连续性,使用数组等数据结构可以提高空间局部性。

        综上所述,高效利用CPU缓存需要综合考虑局部性原理、数据结构的布局、算法选择和了解目标CPU的缓存特性等因素,以最大程度地提高缓存命中率,从而提高程序的性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/703663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Powershell中conda init失效、无法使用conda activate的问题

起因 近期折腾了一下Windows Terminal&#xff0c;安装配置了Powershell 7.3&#xff0c;然后发现conda activate在Powershell中用不了了&#xff0c;conda init powershell不起作用&#xff0c;conda init cmd.exe没有问题 分析 因为powershell的profile.ps1文件路径中存在…

ELK 简介安装

1、概念介绍 日志介绍 日志就是程序产生的&#xff0c;遵循一定格式&#xff08;通常包含时间戳&#xff09;的文本数据。 通常日志由服务器生成&#xff0c;输出到不同的文件中&#xff0c;一般会有系统日志、 应用日志、安全日志。这些日志分散地存储在不同的机器上。 日志…

网络层的DDoS攻击与应用层的DDoS攻击之间的区别

DDoS攻击&#xff08;即“分布是拒绝服务攻击”&#xff09;&#xff0c;是基于DoS的特殊形式的拒绝服务攻击&#xff0c;是一种分布式、协作的大规模攻击方式&#xff0c;主要瞄准一些企业或政府部门的网站发起攻击。根据攻击原理和方式的区别&#xff0c;可以把DDoS攻击分为两…

The Grapes NFT 概览与数据分析

作者&#xff1a;stellafootprint.network 编译&#xff1a;cicifootprint.network 数据源&#xff1a;The Grapes NFT Collection Dashboard The Grapes 是一个有趣且具有吸引力的 NFT 收藏集合&#xff0c;包含 3,333 个精心制作的 NFT。这个 NFT 项目会在 2024 年再创高…

linux僵尸进程

僵尸进程&#xff08;Zombie Process&#xff09;是指在一个进程终止时&#xff0c;其父进程尚未调用wait()或waitpid()函数来获取该进程的终止状态信息&#xff0c;导致进程的资源&#xff08;如进程表中的记录&#xff09;仍然保留在系统中的一种状态。 当一个进程结束时&am…

GO数组解密:从基础到高阶全解

在本文中&#xff0c;我们深入探讨了Go语言中数组的各个方面。从基础概念、常规操作&#xff0c;到高级技巧和特殊操作&#xff0c;我们通过清晰的解释和具体的Go代码示例为读者提供了全面的指南。无论您是初学者还是经验丰富的开发者&#xff0c;这篇文章都将助您更深入地理解…

css美化网页元素

1.span标签的作用 能让某个文字或词语凸显出来 2.字体样式 属性 含义 示例 font-family 设置字体类型 font-family:"隶书"; font-size 设置字体大小 font-size:12px; font-style 设置字体风格 font-style:italic; font-weight 设置字体的粗细 font-we…

java使用Swagger文档报错“java.lang.NullPointerException: null”

java使用Swagger文档报错 一、问题二、解决1、报错2、源码3、方法 一、问题 java项目引入Swagger文档&#xff0c;后期因为一些原因导致Swagger文档不能使用&#xff0c;但是不影响项目运行和正常使用&#xff0c;但每次启动都会报错。 原因有可能是&#xff1a; 1、一个实体类…

手写myscrapy(八)

项目地址&#xff1a;https://gitee.com/wyu_001/myscrapy 接下来接着说明如何多线程运行多个爬虫脚本&#xff1a; 项目的根目录下有个batch.py文件&#xff0c;这个就是批量运行多个爬虫的脚本&#xff0c;这里使用了线程池&#xff0c;同时运行spider下的多个爬虫类&#xf…

编程笔记 Golang基础 038 并发与原子变量

编程笔记 Golang基础 038 并发与原子变量 一、原子操作&#xff08;Atomic Operation&#xff09;二、原子变量三、应用示例 在 Go 语言&#xff08;Golang&#xff09;的并发编程中&#xff0c;原子变量是用于确保多线程安全的重要工具。当多个 Goroutine 并发访问和修改同一变…

【HMAC-SHA1算法以及工作原理】

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 目录 简述概要知识图谱总结 简述概要 连接HMAC-SHA1工作原理以及工具代码 知识图谱 HMAC&#xff08;Hash-based Message Authentication Code&#xff0c;基于散列的消息认证码&#xff09;是一种结合了密钥和消息的认证方法…

刚拿到的《HarmonyOS应用开发者高级认证》,全网整理的题目,将近300题,100%通过

刚拿到《HarmonyOS应用开发者高级认证》&#xff0c;现在把题目和答案分享一下&#xff0c;这些题目是我根据其他网站整理的&#xff0c;宁滥勿缺&#xff0c;有个别题目是重复的&#xff0c;抽半天时间看一下&#xff0c;应该是稳过的。当然建议还是先跟着文档学一下鸿蒙或者看…

Centos 7.5 上nginx设置开机自启动

nginx的安装目录 &#xff1a; /usr/local/nginx 一、没有设置开机自启动前&#xff0c;需要执行/usr/local/nginx/sbin/nginx 启动 二、接下来&#xff0c;我们设置开机自启动&#xff0c;就不用手动启动nginx了 1、cd /usr/lib/systemd/system/ 2、vi nginx.service [un…

如何在Win系统搭建Oracle数据库并实现远程访问【内网穿透】

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

TensorRT及CUDA自学笔记007 运行时库及矩阵加法demo

TensorRT及CUDA自学笔记007 运行时库及矩阵加法demo Runtime 运行时库 明天再补充&#xff0c;先去准备面试了 矩阵加法demo cudaMalloc和cudaMemcpy 它们和c的malloc和memcpy功能一致&#xff0c;只是操作的不是host端的内存空间&#xff0c;而是device端的”显存空间“ …

【深入理解设计模式】适配器设计模式

适配器设计模式 适配器设计模式是一种结构型设计模式&#xff0c;用于将一个类的接口转换成客户端所期望的另一个接口&#xff0c;从而使得原本由于接口不兼容而不能一起工作的类能够一起工作。适配器模式通常用于以下场景&#xff1a; 现有接口与需求不匹配&#xff1a;当需要…

Linux安装Mysql(超详细,亲测)

文章中的全部内容自己都有亲身实践&#xff0c;都是有效的&#xff0c;像常见登录错误中&#xff0c;那种错误的密码修改方式自己以前就浪费了很多事件&#xff0c;还有设置Mysql远程登录这些也是&#xff0c;所以我把这些操作整理了一下&#xff0c;让大家在安装和使用Mysql的…

YOLOv6代码解读[02] configs/hub/yolov6l_finetune.py文件解读

文章目录 模型配置文件骨干网络 CSPBepBackbone颈部网络 CSPRepBiFPNNeck检测头 EffiDeHead构建模型Model 模型配置文件 # YOLOv6l model model dict(typeYOLOv6l,pretrainedweights/yolov6l.pt,depth_multiple1.0,width_multiple1.0,backbonedict(typeCSPBepBackbone,num_re…

多线程基础说明【基础篇】

目录 &#x1f32d;1.相关概念 &#x1f37f;2.创建和启动线程 &#x1f95e;3.线程安全 &#x1f9c8;4.死锁 &#x1f953;5.线程通信的方法 1.相关概念 1.1程序 为完成特定任务&#xff0c;用某种语言编写的一组指令的集合。即指一段静态的代码&#xff0c;静态对象…

都2024年了,软件测试面试都问什么?

1、最熟悉的 selenium 操作&#xff1f; 基本上 selenium 提供的一下几大类操作都能够灵活使用&#xff0c;比如说&#xff1a;八大元素定位方式、三大等待方式、用户点击、输入等常见操作、 还有窗口切换、iframe 切换操作&#xff0c;比如说 actionchains 文件上传、JS操作 等…