编译器优化之内存对齐

news2024/12/23 8:53:15

编译器优化之内存对齐

前言

        在工作中,做性能优化,无意间看到反汇编中有nop指令,大致能猜测是内存对齐相关优化,但不清楚相关优化选项,编来了兴趣,对编译器的内存对齐优化进行一次系统的学习和总结

        由于我编写代码的运行在ARMV8指令集架构服务器上,编译器是gcc,因此后续的总结主要基于ARM指令集。

        这次总结相关实例代码主要通过Compiler Explorer网站提供的在线编译进行编译对比

        自从有了AI后,学习总结变得容易了很多,这次学习和总结也主要基于百度文心一言的对话进行理解

为什么需要内存对齐?

       我们从CPU体系结构中知道,数据/指令加载都依赖缓存,而缓存是按照CacheLine粒度进行数据保存的,我所运行的环境CacheLine大小为64字节(byte);而CPU流水线的fetch阶段也是有带宽的,我的环境是fetch带宽为8条指令32字节(Arm架构指令固定占4字节);这两个都对我们代码的运行效率至关重要;

        想想如果我们有一次循环刚好处理64字节数据,而我们数据的起始地址刚好在64字节开头,我们就只需要使用一条cacheline,数据加载效率就跟高,运行效率当然就更好;

        同样,如果我们代码中函数按照64字节对齐,保障每次函数跳转时,都能将接下来的16条指令同时加载到ICache的一条CacheLine中,那么接下来函数的执行在指令测就不会有太多阻塞。

Gcc相关内存对齐优化:

       这里直接引用文心一言,我觉得回答的十分全面:

  • 属性设置方式:

        __attribute__ ((aligned(n))):这个属性用于指定结构体、类、联合或者一个类型的变量(对象)在分配地址空间时的对齐方式。它确保所作用的对象在编译过程中按n字节对齐。这里的n必须是2的幂次方。如果类型中的成员的自然边界对齐值大于n,则按照机器字长(32位或者64位)对齐。

        __attribute__((packed)):这个属性用于取消结构在编译过程中的优化对齐,使得结构按照最小的对齐方式(通常是1字节)进行排列。这有助于减少结构体的总大小,但可能会降低访问速度,因为CPU可能需要进行额外的内存访问来获取对齐的数据。

  • 修改对齐系数:

        使用pragma宏指令(如#pragma pack(n))可以在编译时修改对齐系数。这会影响后续的结构体或联合体的对齐方式,直到遇到另一个#pragma pack()指令或文件结束。与__attribute__((packed))类似,降低对齐系数可以减少结构体的大小,但可能会降低访问速度。

  • 结构体成员排序:

        将结构体中占用空间较大的成员放在前面,有助于提高内存访问的局部性。这有助于减少CPU缓存未命中的次数,从而提高程序的性能。

        将访问频率较高的成员放在一起,也可以提高内存访问的局部性。

  • 使用静态断言:

        在C++中,可以使用static_assert来确保结构体的对齐满足特定要求。这有助于在编译时捕获潜在的对齐问题。

  • 编译器选项:

        GCC编译器提供了一些选项来影响内存对齐的行为。例如,-falign-functions选项可以用于指定函数的对齐方式;-falign-loops选项可以用于指定循环的对齐方式;-falign-jumps选项可以用于指定跳转指令的对齐方式。这些选项可以根据需要进行调整以优化程序的性能。

  • 平台特定的优化:

        不同的CPU架构和操作系统可能对内存对齐有不同的要求。GCC编译器支持针对特定平台进行优化。例如,使用-mtune=cpu_type选项可以为特定的CPU类型进行优化;使用-march=architecture选项可以指定目标架构的指令集。这些选项有助于确保程序在目标平台上的最佳性能。

       我这里主要对编译器选项、属性设置方式、修改对齐系数三个编译器相关优化进行详细说明

-falign-functions选项

       -falign-functions用来指定函数起始地址对齐格式,如-falign-functions=64,指函数地址以64字节对齐,这样能确保每次函数跳转时,cache和fetch都能够用到最大带宽,举例:

#include <stdio.h>  

void my_function1() {  
    printf("Hello, function1!\n");  
}  

void my_function2() {  
    printf("Hello, function2!\n");  
}  

汇编对比:

在汇编代码中,用nop占位进行地址对齐

-falign-jumps选项

-falign-jumps可以用于指定跳转指令的对齐方式;这样方便在指令跳转时,能保证fetch和cache到的指令能连续运行,提升运行效率;举例:

int square(int num) {
    if(num < 10){
        return num * 10;
    } else if (num < 100){
        return num * 100;
    }
    return num * 200;
}

-falign-loops选项

-falign-loops用于控制循环代码的对齐方式。目的是通过提高循环代码在内存中的对齐度来优化性能,特别是在处理缓存(cache)时。

当处理器执行循环代码时,它通常会从内存中读取循环体中的指令到缓存中。如果循环的起始地址没有与缓存行的起始地址对齐,那么处理器可能需要读取多个缓存行来获取完整的循环体,这会导致额外的内存访问和可能的缓存未命中(cache miss)。缓存未命中会显著增加处理器的等待时间,因为从主内存中读取数据比从缓存中读取要慢得多。举例:

#include <stdint.h>
int test_loop(uint8_t n, int a) 
{
    for(uint8_t i = 0; i < n; ++i) {
        a = a + i;
    }
    return a;
}

__attribute__ ((aligned(n)))设置

__attribute__ ((aligned(n))) 是 GCC 编译器提供的一个特性,用于指定变量或结构体在内存中的对齐方式。这个属性可以确保变量或结构体按照指定的字节数 n 对齐。__attribute__ ((aligned(n))) 可以用于变量或结构体声明;

举例1:变量设置地址对齐

运行结果如下:添加__attribute__ ((aligned(64)))的输出的地址按照64字节对齐,而没有设置对齐属性的,输出地址未对齐

E:\zjtfile\C++\TestDemo\TestDemo>gcc -o2 main.cpp -o main_64

E:\zjtfile\C++\TestDemo\TestDemo>main_64

Address of array: 000000000061FE00

E:\zjtfile\C++\TestDemo\TestDemo>gcc -o2 main.cpp -o main

E:\zjtfile\C++\TestDemo\TestDemo>main

Address of array: 000000000061FE14

举例2:结构体实例对齐

运行结果如下:

E:\zjtfile\C++\TestDemo\TestDemo>gcc -o2 main.cpp -o main_64

E:\zjtfile\C++\TestDemo\TestDemo>main_64

Address of s: 000000000061FE00

E:\zjtfile\C++\TestDemo\TestDemo>gcc -o2 main.cpp -o main

E:\zjtfile\C++\TestDemo\TestDemo>main

Address of s: 000000000061FE10

__attribute__((packed)) 的用法

__attribute__((packed)) 用于指定结构体或联合体(union)的成员在内存中以最小的对齐方式进行布局,即紧凑排列,中间没有填充(padding)字节;__attribute__((packed)) 只能用于结构体或联合体的定义;举例:

#include <stdio.h> 

// 默认情况下,编译器可能会在 char 和 int 之间插入填充字节 
struct DefaultStruct {
    char c;
    int i;
};

// 使用 __attribute__((packed)) 后,char 和 int 之间不会有填充字节 
struct PackedStruct {
    char c;
    int i;
} __attribute__((packed));

int main() 
{
    printf("Size of DefaultStruct: %zu\n", sizeof(struct DefaultStruct));
    // 在 32 位系统上,输出可能是 8(char 1 字节 + 3 字节填充 + int 4 字节) 
    // 在 64 位系统上,输出可能是 12(char 1 字节 + 7 字节填充 + int 4 字节) 

    printf("Size of PackedStruct: %zu\n", sizeof(struct PackedStruct));
    // 输出始终是 5(char 1 字节 + int 4 字节,没有填充) 
    return 0;
}

运行结果如下:

E:\zjtfile\C++\TestDemo\TestDemo>gcc -o2 main.cpp -o main

E:\zjtfile\C++\TestDemo\TestDemo>main

Size of DefaultStruct: 8

Size of PackedStruct: 5

#pragma pack(n)属性

        #pragma pack(n) 是一个预处理指令,用于控制结构体(struct)或联合体(union)中成员的对齐方式。作用是指定数据成员在结构体中的对齐方式,其中 n 是对齐的字节数。当 n 等于 1 时,数据成员会按照紧凑的方式进行排列,不会插入填充字节。当 n 大于 1 时,编译器会尝试将数据成员对齐到 n 字节的边界。

#include <stdio.h> 

#pragma pack(push, 1) // 设置对齐为 1 字节,并保存当前对齐设置 
struct PackedStruct {
    char c;           // 1 字节 
    int i;            // 4 字节(在 32 位系统上) 
};
#pragma pack(pop)    // 恢复之前的对齐设置 

struct DefaultStruct {
    char c;           // 1 字节 
    int i;            // 4 字节(在 32 位系统上),但可能前面有填充字节 
};

int main() {
    printf("Size of PackedStruct: %zu\n", sizeof(struct PackedStruct));
    // 输出:5(char 1 字节 + int 4 字节,没有填充) 
    printf("Size of DefaultStruct: %zu\n", sizeof(struct DefaultStruct));
    // 输出可能是 8(char 1 字节 + 3 字节填充 + int 4 字节),具体取决于编译器和平台
    return 0;
}

运行结果如下:

E:\zjtfile\C++\TestDemo\TestDemo>main

Size of PackedStruct: 5

Size of DefaultStruct: 8

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

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

相关文章

手撕netty源码(四)- ServerBootstrap是如何监听事件的

文章目录 前言一、OP_ACCEPT事件注册1.1 bind 完成之后监听OP_ACCEPT1.2 register0注册完成之后监听OP_ACCEPT 二、事件处理在这里插入图片描述 三、总结 前言 文档中的图片如果不清晰可以直接在线看processOn processOn文档跳转 接上一篇&#xff1a;手撕netty源码&#xff0…

OceanBase开发者大会实录-陈文光:AI时代需要怎样的数据处理技术?

本文来自2024 OceanBase开发者大会&#xff0c;清华大学教授、蚂蚁技术研究院院长陈文光的演讲实录—《AI 时代的数据处理技术》。完整视频回看&#xff0c;请点击这里&#xff1e;> 大家好&#xff0c;我是清华大学、蚂蚁技术研究院陈文光&#xff0c;今天为大家带来《AI 时…

MathType如何使用LaTex代码编辑公式?MathType使用LaTex代码编辑公式教程 mathtype高仿latex

MathType专为解决数学公式输入问题打造&#xff0c;内置有自定义函数识别、国际性字符输入、拖放表达式、标签命名等丰富的功能&#xff0c;下面就来看看如何使用LaTex代码编辑公式吧。 MathType使用LaTex代码编辑公式教程 第一步&#xff1a;首先打开软件&#xff0c;并准备…

WebStorm2024版 将项目上传到gitee

目录 一、准备 WebStorm gitee 二、上传代码到Gitee 三、过程中遇到的问题 报错&#xff1a;You may want to first integrate the remote changes (e.g., git pull ...) before pushing again. 报错&#xff1a;fatal: refusing to merge unrelated histories 报错&a…

鲲鹏华为云--OBS

文章目录 1.创建桶2.上传对象3.下载对象4.分享对象5. 删除对象6.删除桶 1.创建桶 创建桶 2.上传对象 点击创建的桶–“上传对象” 拖拽本地文件或文件夹至“上传对象”区域框内添加待上传的文件。 也可以通过单击“上传对象”区域框内的“添加文件”&#xff0c;选择本地…

【SQL每日一练】统计复旦用户8月练题情况

文章目录 题目一、分析二、题解1.使用case...when..then2.使用if 题目 现在运营想要了解复旦大学的每个用户在8月份练习的总题目数和回答正确的题目数情况&#xff0c;请取出相应明细数据&#xff0c;对于在8月份没有练习过的用户&#xff0c;答题数结果返回0. 示例代码&am…

手撕C语言题典——合并两个有序数组(顺序表)

搭配食用更佳哦~~ 数据结构之顺顺顺——顺序表-CSDN博客 数据结构之顺序表的基本操作-CSDN博客 继续来做一下关于顺序表的经典算法题叭~ 前言 88. 合并两个有序数组 - 力扣&#xff08;LeetCode&#xff09; 合并数组也是力扣上关于顺序表的一道简单题&#xff0c;继续来加深…

由于找不到mfc140u.dll,无法继续执行的多种解决方法

在我们日常与计算机的密切互动中&#xff0c;或许不少用户都曾遇到过这样一个棘手的问题&#xff1a;系统突然弹出一个提示窗口&#xff0c;告知我们“找不到mfc140u.dll文件”。这个文件是Microsoft Foundation Class&#xff08;MFC&#xff09;库的一部分&#xff0c;用于支…

ASP.NET实验室预约系统的设计

摘 要 实验室预约系统的设计主要是基于B/S模型&#xff0c;在Windows系统下&#xff0c;运用ASP.NET平台和SQLServer2000数据库实现实验室预约功能。该设计主要实现了实验室的预约和管理功能。预约功能包括老师对实验室信息、实验项目和实验预约情况的查询以及对实验室的预约…

『MySQL 实战 45 讲』18 - 为什么这些SQL语句逻辑相同,性能却差异巨大

为什么这些SQL语句逻辑相同&#xff0c;性能却差异巨大 条件字段函数操作 创建一个 sql 表。该表为交易记录表&#xff0c;包含交易流水号&#xff08;tradeid&#xff09;、交易员 id&#xff08;operator&#xff09;、交易时间&#xff08;t_modified&#xff09;等字段 …

Python版本管理工具-pyenv

Pyenv是一个Python版本管理工具。 Pyenv允许用户在同一台机器上安装多个版本的Python&#xff0c;并能够轻松切换使用这些版本。 一、安装 Mac下直接使用Homebrew安装 # 更新 Homebrew 的软件列表 brew update # 安装pyenv brew install pyenv# 验证是否安装成功 pyenv -v# …

【水文】LLM 成文测试|Agent AI智能体的未来:技术进步与创新

参与活动&#xff1a;#Agent AI智能体的未来# Agent AI智能体的未来征文活动介绍 随着Agent AI智能体的智能化水平不断提高&#xff0c;它们在未来社会中的角色、发展路径以及可能带来的挑战也引起了广泛关注。快来分享一下你的看法吧~ 活动时间 4月29日-5月13日 内容要求 1、文…

《深入解析Windows操作系统》第5章节学习笔记

1、每个Windows进程都是由一个执行体进程EPROCESS结构来表示的&#xff0c;EPROCESS和相关数据结构位于系统空间&#xff0c;但是进程环境控制块PEB是个例外&#xff0c;它位于进程空间地址中&#xff08;因为它包含了一些需要由用户模式代码来修改的信息&#xff09;。对于每一…

jvm面试题30问

什么是JVM的跨平台&#xff1f; 什么是JVM的语言无关性&#xff1f; 什么是JVM的解释执行 什么是JIT? JIT&#xff1a;在Java编程语言和环境中&#xff0c;即时编译器&#xff08;JIT compiler&#xff0c;just-in-time compiler&#xff09;是一个把Java的字节码&#xff08;…

基于springboot实现英语知识应用网站系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现英语知识应用网站系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了英语知识应用网站的开发全过程。通过分析英语知识应用网站管理的不足&#xff0c;创建了一个计算机管理英语知识应…

Mysql中索引的概念

索引相关概念 基础概念&#xff1a; 在MySQL中&#xff0c;索引是一种数据结构&#xff0c;用于加快数据库查询的速度和性能。索引可以帮助MySQL快速定位和访问表中的特定数据&#xff0c;就像书籍的索引一样&#xff0c;通过存储指向数据行的指针&#xff0c;可以快速…

cmake的使用方法: 编译生成库文件

一. 简介 前面文章学习了针对单个 .c文件&#xff0c;cmake 工具是如何编译的&#xff1f; 针对包含多个 .c文件&#xff0c;cmake工具又是如何编译的&#xff1f;文章如下&#xff1a; cmake的使用方法: 单个源文件的编译-CSDN博客 cmake的使用方法: 多个源文件的编译-CS…

Allegro如何删除死铜

Allegro如何删除死铜 敷铜说明 敷铜后铜皮会避让与自己不同的信号,避让后有的铜皮就会变成很小一块,这个最小形状的设置如下, 首先点击: 然后选中对应的铜皮: 在鼠标右键,选择“Parameters…” 改变上图红色框中的数值就能设置最小死铜的大小。 删除死铜的方法 1、首…

设计模式之装饰者模式DecoratorPattern(四)

一、概述 装饰者模式&#xff08;Decorator Pattern&#xff09;是一种用于动态地给一个对象添加一些额外的职责的设计模式。就增加功能来说&#xff0c;装饰者模式相比生成子类更为灵活。装饰者模式是一种对象结构型模式。 装饰者模式可以在不改变一个对象本身功能的基础上增…

前端开发报错:Extension context invalidated

这个错误一般是由浏览器的插件导致的 直接卸载 就没有问题了 注意检查 可能不是你自己的代码问题哦&#xff5e;