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/704600.shtml

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

相关文章

【C++】类和对象(2)

目录 1. 初始化列表 2.explicit关键字 3. Static成员 3. 友元 3.1友元函数 3.2友元类 4. 内部类 5.匿名对象 1. 初始化列表 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值&#xff0c;但是这个过程并不能称为对对…

3,设备无关位图显示

建立了一个类Dib Dib.h #pragma once #include “afx.h” class CDib :public CObject { public: CDib(); ~CDib(); char* GetFileName(); BOOL IsValid(); DWORD GetSize(); UINT GetWidth(); UINT GetHeight(); UINT GetNumberOfColors(); RGBQUAD* GetRGB(); BYTE* GetDat…

flutter sliver 多种滚动组合开发指南

flutter sliver 多种滚动组合开发指南 视频 https://youtu.be/4mho1kZ_YQU https://www.bilibili.com/video/BV1WW4y1d7ZC/ 前言 有不少同学工作中遇到需要把几个不同滚动行为组件&#xff08;顶部 appBar、内容固定块、tabBar 切换、tabBarView视图、自适应高度、横向滚动&a…

Linux开发:pam6 check_user示例

linux-pam里提供了一个简单的pam应用示例:check_user #include <security/pam_appl.h> #include <security/pam_misc.h> #include <stdio.h>static struct pam_conv conv = {misc_conv,NULL };int main(int argc, char *argv[]) {pam_handle_t *pamh=NULL;…

软硬协同设计下的飞天盘古,是如何降低存储系统开销的?

云布道师 经过十几年的技术演进&#xff0c;阿里巴巴已经实现了统一存储的目标——即以“飞天盘古”系统作为统一底座&#xff0c;通过标准化、服务化和开放化的方式建立了完整的存储产品和服务体系&#xff0c;服务广大内部和外部客户。 “万古乾坤心上辟&#xff0c;于令日…

正大国际:董宇辉最治愈的六句话

关于努力 努力不是为了证明自己多优秀&#xff0c; 而是在意外和不可控的因素来临时&#xff0c; 那些平常所努力积淀的涵养和能力&#xff0c; 可以成为抗衡一切风雨的底气。 关于焦虑 焦虑是对的。 焦虑是因为你想做得更好&#xff0c; 说明你追求高&#xff0c; 说明你眼界…

基于51单片机的智能监护与健康检测[proteus仿真]

基于51单片机的自行车测速系统设计[proteus仿真] 个人健康检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的智能监护与健康检测 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xff…

LeetCode 2356.每位教师所教授的科目种类的数量

表: Teacher ----------------- | Column Name | Type | ----------------- | teacher_id | int | | subject_id | int | | dept_id | int | ----------------- 在 SQL 中&#xff0c;(subject_id, dept_id) 是该表的主键。 该表中的每一行都表示带有 teacher_id 的教师在系 d…

学习总结23

# [NOIP2002 普及组] 选数 ## 题目描述 已知 n 个整数 x1,x2,……,xn&#xff0c;以及 1 个整数 k&#xff08;k<n&#xff09;。从 n 个整数中任选 k 个整数相加&#xff0c;可分别得到一系列的和。例如当 n4&#xff0c;k3&#xff0c;4 个整数分别为 3,7,12,19 时&…

CrossOver24破解版下载安装与激活

在 Mac 上运行Windows 软件&#xff0c;CrossOver Mac 可以轻松地从 Dock 本地启动 Windows 应用程序&#xff0c;并将 Mac 操作系统功能&#xff08;如跨平台复制和粘贴以及共享文件系统&#xff09;集成到您的 Windows 程序中。 CrossOver 产品特性 无需重启 CrossOver 可以…

全连接层是什么

个人浅显的看法: 当前层的每一个神经元&#xff0c;都和下一层的每一个神经元有连接&#xff0c;叫全连接层。 当前层有n个神经元&#xff0c;下一层有m个神经元&#xff0c;则全连接层&#xff0c;当前层的n个神经元和下一层m个神经元都有连接

Node.js基础---http模块

概念 http模块是 Node.js 官网提供用来创建 web服务器 的模块&#xff0c;通过 http.createServer&#xff08;&#xff09;创建 服务器和普通电脑区别&#xff1a;服务器有web服务器软件&#xff0c;IIS&#xff0c;Apache&#xff0c;phpstudy 在node内不需要 使用 先导入…

致专升本的大家

亲爱的专升本学员 在你踏上专升本的征程之际&#xff0c;我想告诉你&#xff0c;这是一段充满挑战但也充满希望的旅程。在这个过程中&#xff0c;你将会面临各种困难和压力&#xff0c;但请记住&#xff0c;每一次的努力都是为了迈向更好的未来。 首先&#xff0c;要明白一点…

LeetCode69. x 的平方根(C++)

LeetCode69. x 的平方根 题目链接代码 题目链接 https://leetcode.cn/problems/sqrtx/description/ 代码 class Solution { public:int mySqrt(int x) {int right x, left 0, ans -1;while(left < right){long long mid left (right - left) / 2;if(mid * mid <…

openssl3.2 - crypto-mdebug被弃用后, 内存泄漏检查的替代方法

文章目录 openssl3.2 - crypto-mdebug被弃用后, 内存泄漏检查的替代方法概述笔记查看特性列表openssl3.2编译脚本 - 加入enable-crypto-mdebug看看有没有替代内存诊断的方法?main.cppmy_openSSL_lib.hmy_openSSL_lib.c备注备注这招不行啊显势调用默认上下文也不行END openssl3…

Laravel03 路由到控制器与连接数据库

Laravel03 路由到控制器与连接数据库 1. 路由到控制器2. 连接数据库 1. 路由到控制器 如下图一些简单的逻辑处理可以放在web.php中&#xff0c;也就是路由的闭包函数里面。但是大的项目&#xff0c;我们肯定不能这么写。 为什么保证业务清晰好管理&#xff0c;都应该吧业务逻辑…

Amazon Generative AI | 基于 Amazon 扩散模型原理的代码实践之采样篇

以前通过论文介绍 Amazon 生成式 AI 和大语言模型&#xff08;LLMs&#xff09;的主要原理之外&#xff0c;在代码实践环节主要还是局限于是引入预训练模型、在预训练模型基础上做微调、使用 API 等等。很多开发人员觉得还不过瘾&#xff0c;希望内容可以更加深入。因此&#x…

python 进程笔记二(通讯) (概念+示例代码)

1、为什么要掌握进程间通信 Python代码效率由于受制于GIL全局锁限制&#xff0c;多线程不能利用多核CPU来加速&#xff0c;而多进程方式却可以绕过GIL限制, 发挥多CPU加速的优势&#xff0c;达到提高程序的性能的目的。 然而进程间通信却是不得不考虑的问题。 进程不同于线程&a…

react useMemo 用法

1&#xff0c;useCallback 的功能完全可以由 useMemo 所取代&#xff0c;如果你想通过使用 useMemo 返回一个记忆函数也是完全可以的。 usecallback(fn,inputs)is equivalent to useMemo(()> fn, inputs). 区别是:useCallback不会执行第一个参数函数&#xff0c;而是将它返…

Java Swing游戏开发学习2

跟随大佬教程继续&#xff0c;图片资源&#xff0c;视频简介有下载链接。 这个文章是看视频教程写的&#xff0c;不算原创。有条件的可以去油管搜索RyiSnow&#xff0c;是一个游戏开发视频制作up主&#xff0c;讲解的非常基础&#xff0c;可以边看边实践&#xff0c;增加对Java…