“我们仍需共生命的慷慨与繁华相爱,即使岁月以刻薄和荒芜相欺”
文章目录
- 前言
- 文章有误敬请斧正 不胜感恩!
- 第一题:
- ***什么是gcc:***
- C 语言中,“gcc -O2”是使用 GCC 编译器时的一个编译选项。
- 第一部分:为什么程序一输出0,而程序二输出1?
- 第二题:
- 第二部分:为什么两个循环版本的性能有差异?
- Cache机制:
- 总结
前言
写在开始:
前几天碰到了两个特别有意思的题目,关于C语言的GCC的优化和数组的存放方式、Cache机制、访问局部性.
话不多说我们一起来看一下这两道题目!
文章有误敬请斧正 不胜感恩!
以下是本篇文章正文内容,
第一题:
首先来看
什么是gcc:
在 C 语言中,GCC(GNU Compiler Collection)是一种常用的编译器。
一、编译过程
- 预处理:对 C 源文件中的预处理指令(如#include、#define 等)进行处理,生成扩展后的源代码。
- 编译:将预处理后的源代码转换为汇编代码。
- 汇编:把汇编代码转换为机器代码,生成目标文件。
- 链接:将多个目标文件以及所需的库文件链接在一起,生成可执行文件。
二、常用命令选项
- -o :指定输出文件的名称。例如,“gcc -o main main.c”将编译 main.c 并生成名为 main 的可执行文件。
- -Wall :开启所有警告信息,帮助开发者发现潜在的问题。
- -g :生成调试信息,以便在调试器中进行程序调试。
三、优势
- 开源免费:可以自由使用和修改,降低了开发成本。
- 跨平台:可在多种操作系统上运行,方便开发者进行移植。
- 功能强大:支持多种优化选项和高级语言特性,能够生成高效的代码。
C 语言中,“gcc -O2”是使用 GCC 编译器时的一个编译选项。
一、含义
“-O2”代表优化级别为 2。它指示编译器在编译代码时进行一定程度的优化,以提高生成的可执行程序的性能。
二、优化内容
- 函数内联:将一些小的、频繁调用的函数直接展开在调用处,减少函数调用的开销。
- 循环优化:对循环进行各种优化,如循环展开、强度削减等,以提高循环的执行效率。
- 常量传播:将已知的常量值传播到使用该常量的地方,减少计算量。
三、注意事项
1. 虽然优化可以提高性能,但也可能导致一些难以察觉的问题。例如,优化后的代码可能与未优化的代码在行为上略有不同,特别是在涉及未定义行为的情况下。
2. 在调试程序时,最好不要使用优化选项,因为优化后的代码可能会使调试变得更加困难。等程序调试完成后,再使用优化选项进行编译以提高性能。
3. 不同版本的 GCC 对优化的实现可能会有所不同,所以在使用“-O2”选项时,最好对生成的代码进行充分的测试,以确保其正确性和性能。
所以第一题的问题就出在这里。
乍一看,这两个程的运行结果应该是一样的,但是我我们可以发现
第二个程序,除了a和b的比较之外,增加了第三个f(10)调用赋值给c。
这就会牵涉到gcc的优化问题。
第一部分:为什么程序一输出0,而程序二输出1?
程序一与程序二的主要区别在于第三个f(10)
的调用。由于优化级别是-O2
,GCC编译器会进行一些优化操作。
-
程序一:两个变量
a
和b
分别调用了f(10)
。虽然函数f
是一个简单的数学计算(返回1.0 / x
),但是编译器可能会将两次调用结果缓存或优化掉,因此a == b
的比较可能总是返回1,输出为0的可能是由于浮点数比较的精度问题。 -
程序二:除了
a
和b
的比较之外,增加了第三个f(10)
调用赋值给c
。这个额外的调用可能改变了编译器的优化行为,使得浮点数比较更加精确,导致a == b
判断为真,所以输出1。
由于浮点数计算中存在一定的精度误差,以及GCC的优化行为不同,程序一和程序二的输出结果可能会不同。这可能是因为浮点数运算在高优化级别下被进一步精简或缓存,导致结果不一致。
第二题:
第二部分:为什么两个循环版本的性能有差异?
首先?什么是二维数组,二维数组和一维数组一样,一维数组存放元素,二维数组存放一维数组,实质也是存储数据的一个容器对象。
public static void main(String [] args){
//定义int类型的二维数组
int[][] arrs= new int [3][2];
System.out.println(arrs);
}
//赋值
arrs[0][0]=1;
arrs[0][0]=2;
arrs[1][0]=3;
arrs[1][1]=4;
arrs[2][0]=5;
arrs[2][1]=6;
赋值之后的数组:
有了这个理解,我们再讨论遍历
什么是遍历二维数组?
所谓的遍历二维数组,实际是遍历一维数组,然后遍历每个一维数组的信息。
按列顺序访问时,虽然算法逻辑上是正确的,(数据结构里面的,顺序结构逻辑上顺序和顺序存储,数据是,物理上顺序),
但每次访问的内存地址不连续,缓存命中率低,因此性能下降,执行速度会降低
(个人理解)
所以,第二个肯定更慢
详解:
两个程序的功能是相同的,只是嵌套循环的顺序不同:
-
copyij:
for(i = 0; i < 2048; i++)
外层循环遍历行,for(j = 0; j < 2048; j++)
内层循环遍历列。 -
copyji:相反,外层遍历列,内层遍历行。
两者的计算复杂度都是O(n²),但是访问内存的方式不同:
- Cache局部性:现代处理器在访问内存时使用缓存(cache)来加速操作。当连续访问相邻的内存地址时,缓存的命中率更高,速度更快。
copyij
版本中,每次访问src[i][j]
和dst[i][j]
时,内存访问是按行顺序进行的,这符合内存连续性,能更好地利用CPU缓存。- 而在
copyji
版本中,按列顺序访问时,虽然算法逻辑上是正确的,但每次访问的内存地址不连续,缓存命中率低,因此性能下降,执行速度变慢了。
所以:Cache机制是导致两个程序性能差异的关键。
Cache机制:
以下我们来详细谈谈Cache机制:
Cache 机制即缓存机制。
一、定义与作用
- 定义:Cache 是一种高速存储区域,用于临时存放数据,以减少对较慢存储设备的访问次数。
- 作用:提高数据访问速度,减少系统响应时间,提升系统性能。例如,在计算机系统中,CPU 高速缓存可以大大加快数据处理速度;在网页浏览中,浏览器缓存可以快速加载之前访问过的页面资源。
二、工作原理
当系统需要访问数据时,首先会检查 Cache 中是否存在所需数据。如果存在,直接从 Cache 中读取,速度非常快;如果不存在,则从较慢的存储设备(如内存、硬盘等)中读取数据,并将其存入 Cache 中,以便下次访问时可以更快地获取。
三、常见类型
- CPU 缓存:分为一级缓存(L1 Cache)、二级缓存(L2 Cache)和三级缓存(L3 Cache)等,离 CPU 核心越近的缓存速度越快但容量越小。
- 浏览器缓存:存储网页的静态资源(如图片、脚本、样式表等),当再次访问同一页面时,可以直接从缓存中加载资源,减少网络请求。
- 数据库缓存:缓存数据库中的查询结果,当相同的查询再次执行时,可以直接返回缓存中的结果,提高数据库查询性能。
四、管理策略
- 替换策略:当 Cache 已满且需要存储新数据时,需要选择一个旧数据进行替换。常见的替换策略有先进先出(FIFO)、最近最少使用(LRU)和随机替换等。
- 写入策略:决定当数据在 Cache 中被修改后,如何同步到较慢的存储设备中。常见的写入策略有写直达(Write-through)和写回(Write-back)等。
总结
cache机制是我们解题的关键。以上,便是我们今天学习的内容,我们下篇文章再见。
标签:GCC,缓存,Cache,C语言,访问,遍历,数组,优化 From: https://blog.csdn.net/2301_79175212/article/details/142282893