首页 > 其他分享 >C语言中的GCC的优化和数组的存放方式、Cache机制、访问局部性

C语言中的GCC的优化和数组的存放方式、Cache机制、访问局部性

时间:2024-09-16 14:20:25浏览次数:3  
标签:GCC 缓存 Cache C语言 访问 遍历 数组 优化

“我们仍需共生命的慷慨与繁华相爱,即使岁月以刻薄和荒芜相欺”

文章目录

  • 前言
    • 文章有误敬请斧正 不胜感恩!
    • 第一题:
      • ***什么是gcc:***
      • C 语言中,“gcc -O2”是使用 GCC 编译器时的一个编译选项。
      • 第一部分:为什么程序一输出0,而程序二输出1?
    • 第二题:
      • 第二部分:为什么两个循环版本的性能有差异?
    • Cache机制:
  • 总结


前言

写在开始:

前几天碰到了两个特别有意思的题目,关于C语言的GCC的优化和数组的存放方式、Cache机制、访问局部性.

话不多说我们一起来看一下这两道题目!
在这里插入图片描述


文章有误敬请斧正 不胜感恩!

以下是本篇文章正文内容,


第一题:

请添加图片描述
首先来看

什么是gcc:

在 C 语言中,GCC(GNU Compiler Collection)是一种常用的编译器。

一、编译过程

  1. 预处理:对 C 源文件中的预处理指令(如#include、#define 等)进行处理,生成扩展后的源代码。
  2. 编译:将预处理后的源代码转换为汇编代码。
  3. 汇编:把汇编代码转换为机器代码,生成目标文件。
  4. 链接:将多个目标文件以及所需的库文件链接在一起,生成可执行文件。

二、常用命令选项

  1. -o :指定输出文件的名称。例如,“gcc -o main main.c”将编译 main.c 并生成名为 main 的可执行文件。
  2. -Wall :开启所有警告信息,帮助开发者发现潜在的问题。
  3. -g :生成调试信息,以便在调试器中进行程序调试。

三、优势

  1. 开源免费:可以自由使用和修改,降低了开发成本。
  2. 跨平台:可在多种操作系统上运行,方便开发者进行移植。
  3. 功能强大:支持多种优化选项和高级语言特性,能够生成高效的代码。

C 语言中,“gcc -O2”是使用 GCC 编译器时的一个编译选项。

一、含义
“-O2”代表优化级别为 2。它指示编译器在编译代码时进行一定程度的优化,以提高生成的可执行程序的性能。

二、优化内容

  1. 函数内联:将一些小的、频繁调用的函数直接展开在调用处,减少函数调用的开销。
  2. 循环优化:对循环进行各种优化,如循环展开、强度削减等,以提高循环的执行效率。
  3. 常量传播:将已知的常量值传播到使用该常量的地方,减少计算量。

三、注意事项

1. 虽然优化可以提高性能,但也可能导致一些难以察觉的问题。例如,优化后的代码可能与未优化的代码在行为上略有不同,特别是在涉及未定义行为的情况下。
2. 在调试程序时,最好不要使用优化选项,因为优化后的代码可能会使调试变得更加困难。等程序调试完成后,再使用优化选项进行编译以提高性能。
3. 不同版本的 GCC 对优化的实现可能会有所不同,所以在使用“-O2”选项时,最好对生成的代码进行充分的测试,以确保其正确性和性能。

所以第一题的问题就出在这里。

乍一看,这两个程的运行结果应该是一样的,但是我我们可以发现
第二个程序,除了a和b的比较之外,增加了第三个f(10)调用赋值给c。
这就会牵涉到gcc的优化问题。

第一部分:为什么程序一输出0,而程序二输出1?

程序一与程序二的主要区别在于第三个f(10)的调用。由于优化级别是-O2,GCC编译器会进行一些优化操作。

  1. 程序一:两个变量ab分别调用了f(10)。虽然函数f是一个简单的数学计算(返回1.0 / x),但是编译器可能会将两次调用结果缓存或优化掉,因此a == b的比较可能总是返回1,输出为0的可能是由于浮点数比较的精度问题。

  2. 程序二:除了ab的比较之外,增加了第三个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;

赋值之后的数组:

在这里插入图片描述
有了这个理解,我们再讨论遍历
什么是遍历二维数组?
所谓的遍历二维数组,实际是遍历一维数组,然后遍历每个一维数组的信息。

按列顺序访问时,虽然算法逻辑上是正确的,(数据结构里面的,顺序结构逻辑上顺序和顺序存储,数据是,物理上顺序),
但每次访问的内存地址不连续,缓存命中率低,因此性能下降,执行速度会降低
(个人理解)
所以,第二个肯定更慢

详解:
两个程序的功能是相同的,只是嵌套循环的顺序不同:

  1. copyijfor(i = 0; i < 2048; i++) 外层循环遍历行,for(j = 0; j < 2048; j++) 内层循环遍历列。

  2. copyji:相反,外层遍历列,内层遍历行。

两者的计算复杂度都是O(n²),但是访问内存的方式不同:

  • Cache局部性:现代处理器在访问内存时使用缓存(cache)来加速操作。当连续访问相邻的内存地址时,缓存的命中率更高,速度更快。
    • copyij版本中,每次访问src[i][j]dst[i][j]时,内存访问是按行顺序进行的,这符合内存连续性,能更好地利用CPU缓存。
    • 而在copyji版本中,按列顺序访问时,虽然算法逻辑上是正确的,但每次访问的内存地址不连续,缓存命中率低,因此性能下降,执行速度变慢了。

所以:Cache机制是导致两个程序性能差异的关键。

Cache机制:

以下我们来详细谈谈Cache机制

Cache 机制即缓存机制。

一、定义与作用

  • 定义:Cache 是一种高速存储区域,用于临时存放数据,以减少对较慢存储设备的访问次数。
  • 作用:提高数据访问速度,减少系统响应时间,提升系统性能。例如,在计算机系统中,CPU 高速缓存可以大大加快数据处理速度;在网页浏览中,浏览器缓存可以快速加载之前访问过的页面资源。

二、工作原理
当系统需要访问数据时,首先会检查 Cache 中是否存在所需数据。如果存在,直接从 Cache 中读取,速度非常快;如果不存在,则从较慢的存储设备(如内存、硬盘等)中读取数据,并将其存入 Cache 中,以便下次访问时可以更快地获取。

三、常见类型

  1. CPU 缓存:分为一级缓存(L1 Cache)、二级缓存(L2 Cache)和三级缓存(L3 Cache)等,离 CPU 核心越近的缓存速度越快但容量越小。
  2. 浏览器缓存:存储网页的静态资源(如图片、脚本、样式表等),当再次访问同一页面时,可以直接从缓存中加载资源,减少网络请求。
  3. 数据库缓存:缓存数据库中的查询结果,当相同的查询再次执行时,可以直接返回缓存中的结果,提高数据库查询性能。

四、管理策略

  1. 替换策略:当 Cache 已满且需要存储新数据时,需要选择一个旧数据进行替换。常见的替换策略有先进先出(FIFO)、最近最少使用(LRU)和随机替换等。
  2. 写入策略:决定当数据在 Cache 中被修改后,如何同步到较慢的存储设备中。常见的写入策略有写直达(Write-through)和写回(Write-back)等。

总结

cache机制是我们解题的关键。以上,便是我们今天学习的内容,我们下篇文章再见。


在这里插入图片描述

标签:GCC,缓存,Cache,C语言,访问,遍历,数组,优化
From: https://blog.csdn.net/2301_79175212/article/details/142282893

相关文章

  • GCC安全编译选项
    以CMake为例,给出安全编译选项的定义。关闭RPATH特性。set(CMAKE_SKIP_RPATHTRUE)开启栈保护。set(CMAKE_CXX_FLAGS"${CMAKE_CXX_FLAGS}-fstack-protector-strong")或者set(CMAKE_CXX_FLAGS"${CMAKE_CXX_FLAGS}-fstack-protector-all")开启GOT表保护。set(CM......
  • 鹏哥C语言39---分支/循环语句练习:猜数字游戏
    #define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<time.h>//voidfun(inta[]) //因为传过来的是地址,所以应该用一个指针变量来接收,故这里的a本质上是个指针变量//{//   printf("%zu",sizeof(a));//输出8 在x64下,指针大小是......
  • 【C语言】 结构体与位段
    系列文章目录C结构体与位段文章目录系列文章目录前言一、结构体的定义与声明1.结构体的定义2.结构体类型的声明结构的声明结构体变量的创建和初始化3.结构的特殊声明4.结构的自引用二、结构体内存对齐1.对齐规则为什么存在内存对齐?修改默认对齐数三、结构体传参......
  • Python调用C语言动态链接库
    调用方法如果觉得Python性能不够,可以使用C、C++或Rust、Golang为按标准C类型。为Python编写扩展。Python通过自带的ctypes模块,可以加载并调用C标准动态链接库(如.ddl或.so)中的函数。常用的操作为:importctypes#加载动态链接库lib=ctypes.CDLL("./xxx.so")#声明要调......
  • C语言:链表
    链表是一种常见的基础数据结构,它由一系列节点(Node)组成。每个节点包含两部分:数据域(存储数据)和指针域(存储下一个节点的地址)。链表的特点是元素在内存中不一定连续存储,而是通过指针连接起来。以下是链表的一些基本特点:动态性:链表的长度可以动态变化,不需要在创建时指定大小。灵活......
  • C语言:结构体
    一、结构体的概念和定义1.为什么要定义结构体结构体是由用户自己定义(设计)的数据类型。其实就是各种信息的打包。比如说,每个学生都有学号、姓名和成绩,100个学生就有100份这种数据,打包起来整合就会方便很多。2.结构体定义的格式struct[结构体名]{    成员列表}......
  • C语言一些简单的细节记录
    一、声明和定义的区别1.声明(Declaration):是告诉编译器有一个变量、函数或类型存在,但不为其分配内存或提供具体的实现。声明提供了有关标识符(如变量名、函数名)的信息,包括类型和名称。它们通常在头文件中出现,以便在多个源文件中共享。例如,以下是变量、函数和类型的声明示例:......
  • c语言写的环形队列
            以下是一个简单的环形队列的实现示例,包括初始化、入队、出队、查询当前元素个数、队列长度和队列元素可视化。        这里使用了静态数组来实现队列的存储,适合于固定大小的队列。#include<stdio.h>#defineMAX_QUEUE_SIZE10 //定义队列的最大......
  • 带你深入了解C语言指针(三)
    目录前言一、字符指针变量字符数组与常量字符串二、数组指针变量1.数组指针变量是什么2.数组指针变量怎么初始化3.数组指针怎么利用?三、二维数组传参四、函数指针变量1.函数指针变量的创建2.函数指针变量的使用3.typedef4.define和typedef的区别五、函数指针数......
  • RHEL8下的IRIS CACHE数据库部署
    一、概述IRIS是数据库管理平台,安装IRIS+Caché相当于安装完整MySQL。EPIC基于Caché开发了Chronicles管理工具,医院系统使用EPIC系统时,通常使用Chronicles操作数据库。IRIS提供完整的MySQL安装实例,包括数据库、管理工具和连接工具。二、部署1、环境准备本次测试的环境采用虚拟主......