什么是内存对齐

news2024/11/16 19:30:03
  • 内存对齐
    • 什么是内存对齐
    • 为什么要内存对齐
    • 内存对齐的规则
    • 结构体中内存对齐
      • sizeof
      • 无嵌套
      • 有嵌套
    • iOS中对象内存对齐
      • iOS中获取内存大小方式
        • class_getInstanceSize()
        • malloc_size()
      • iOS中内存对齐
        • 实际占用内存对齐方式
        • 系统分配内存对齐方式
        • 问题
      • 内存优化
        • 总结

内存对齐

什么是内存对齐

  • 元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小(通常它为4或8)来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是所谓的内存对齐。

为什么要内存对齐

  • 有些CPU可以访问任意地址上的任意数据,而有些CPU只能在特定地址访问数据,因此不同硬件平台具有差异性,这样的代码就不具有移植性,如果在编译时,将分配的内存进行对齐,这就具有平台可以移植性了
  • CPU每次寻址都是要消费时间的,并且CPU 访问内存时,是以字长(word size)为单位访问,所以数据结构应该尽可能地在自然边界上对齐,如果访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问,内存对齐后可以提升性能。这是一种以空间换时间的方法,目的降低cpu开销。
  • 举例:
    • 假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器。这需要做很多工作。
    • 现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

内存对齐的规则

每个特定平台上的编译器都有自己的默认"对齐系数",常用平台默认对齐系数如下:(32位系统对齐系数是4,64位系统对齐系数是8)。这只是默认对齐系数,实际上对齐系数我们是可以修改的,程序员可以通过预编译命令#pragma pack(),n = 1, 2, 4, 8, 18来改变这一系数,其中的n就是你要指定的“对齐系数”

  1. 原则一:数据成员对⻬规则
    • 结构体(struct)或者联合体(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如 int 4字节,那么存储位置可以是0-4-8-12-16-20 依次类推)
    • 数据成员的对齐规则可以理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置
  2. 原则二:数据成员为结构体
    • 如果一个结构体里有某些结构体成员,则该结构体成员要从其内部最大元素大小的整数倍地址开始存储,比如struct a里有struct bb里面有char, int, double, short,那么b应该从8的整数倍地址开始存储,即最大成员为double 8字节
  3. 原则三:结构(或联合)的整体对齐规则
    • 在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的整数倍
    • 比如说:编译器指定按照8字节对齐,然后结构体最大数据成员长度为4,最终按照4字节对齐

结构体中内存对齐

  • 根据上面的内存对齐规则,我们探索一下结构体中内存对齐

sizeof

  • sizeof 是一个操作符,不是函数
  • 我们一般使用sizeof计算内存大小时,传入的对象主要是数据类型,这个是在编译阶段就会确定大小而不是运行时。sizeof最终得到的结果是该数据类型占用空间的大小

无嵌套

struct Struct1 {
    double a;
    char b;
    int c;
    short d;
}struct1;

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}struct2;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%lu-%lu", sizeof(struct1), sizeof(struct2));
    }
    return 0;
}

打印结果:内存对齐[8322:58962] 24-16

  • 根据结果发现,结构体中包含的变量相同,只是因为位置不一样,但是内存大小不一样
  • 根据内存对齐原则,来分析一下结构体中成员的内存占用
  • Struct1中:
    • 变量a: 占8个字节,offset0开始, min(0,8), 即0 ~ 7 存放a
    • 变量b: 占1个字节,offset8开始, min(8,1), 即8 存放d
    • 变量c: 占4个字节,offset9开始, min(9,4)9 % 4 != 0,继续往后移动直到找到可以整除4的位置 1212 ~ 15 存放b
    • 变量d: 占2个字节,offset16开始,min(16,2),即16 ~ 17 存放c
    • 实际占用18个字节,根据内存对齐原则三,结构体中最大变量a占用8字节,所有最终大小必须是8字节的倍数,最终占用24个字节
  • Struct2中:
    • 变量a: 占8个字节,offset0开始, min(0,8), 即0 ~ 7 存放a
    • 变量b: 占4个字节,offset8开始, min(8,4), 即8 ~ 11 存放b
    • 变量c: 占2个字节,offset12开始,min(12,2),即12 ~ 13 存放c
    • 变量d: 占1个字节,offset14开始,min(14,1),即14 存放d
    • 实际占用了15个字节,根据内存对齐原则三,结构体中最大变量a占用8字节,所有最终大小必须是8字节的倍数,最终占用16个字节。

有嵌套

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}struct2;

struct Struct3 {
    double a;
    char b;
    int c;
    short d;
    struct Struct2 str;
}struct3;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%lu-%lu", sizeof(struct2), sizeof(struct3));
    }
    return 0;
}

打印结果:内存对齐[36173:182032] 16-40

  • Struct3中:
    • 变量a: 占8个字节,offset0开始, min(0,8), 即0 ~ 7 存放a
    • 变量b: 占4个字节,offset8开始, min(8,4), 即8 ~ 11 存放b
    • 变量c: 占2个字节,offset12开始,min(12,2),即12 ~ 13 存放c
    • 变量d: 占1个字节,offset14开始,min(14,1),即14 存放d
    • 变量str,由于它是结构体变量,根据内存对齐规则二:结构体成员要从其内部最大元素大小的整数倍地址开始存储。结构体中最大变量占8字节,所以offset需要从16开始,Struct2的内存大小是18字节。所以min(16, 18),即16-33存放str
    • 实际内存大小是34字节,根据内存对齐原则三,结构体中最大变量a占用8字节,所有最终大小必须是8字节的倍数,最终占用40个字节

iOS中对象内存对齐

iOS中获取内存大小方式

  1. sizeof()在上一小节已经讲述
  2. class_getInstanceSize()获取的是一个对象实际占用的内存空间
  3. malloc_size()获取的是系统实际给开辟的内存空间大小
  • 我们具体看一下后两个实现

class_getInstanceSize()

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
  • 内部调用了alignedInstanceSize()方法
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}
  1. 我们需要获取未内存对齐大小unalignedInstanceSize()
// May be unaligned depending on class's ivars.
// 可以根据类的成员变量进行对齐。
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
}
  • 该方法内部是获取类的成员变量大小
  1. 对获取到的内存大小,进行对齐
#define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
  • 此处采用的是8字节对齐,也就是说对象中成员变量按照8字节对齐。
  • 具体计算过程如下:假设x为8字节
    1. (x + WORD_MASK) = (8 + 7) = 15,用二进制表示:0000 1111
    2. ~WORD_MASK = ~7,用二进制表示:1111 1000
    3. 15 & ~7,用二进制表示:0000 1000
    4. 最终结果8个字节

malloc_size()

  • 获取的是系统实际给开辟的内存空间大小,采用了16字节对齐。

iOS中内存对齐

实际占用内存对齐方式

  • 通过class_getInstanceSize()方法,我们可以看到内部是通过8字节对齐
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

系统分配内存对齐方式

  • 调用系统分配的流程可以查看Runtime源码解析-alloc,里面有完整的alloc流程
  • 这里我们只需要知道,最终系统分配内存时,调用instanceSize方法来获取大小。
inline size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        return cache.fastInstanceSize(extraBytes);
    }

    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;  
    return size;
}
  • 这里一般都会进入fastInstanceSize方法
size_t fastInstanceSize(size_t extra) const
{
    ASSERT(hasFastInstanceSize(extra));

    if (__builtin_constant_p(extra) && extra == 0) {
        return _flags & FAST_CACHE_ALLOC_MASK16;
    } else {
        size_t size = _flags & FAST_CACHE_ALLOC_MASK;
        // remove the FAST_CACHE_ALLOC_DELTA16 that was added
        // by setFastInstanceSize
        return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
    }
}

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}
  • 该方法最后调用了align16()方法,进行16字节对齐。该align16()方法和word_align()内部实现类似,具体如何16字节对齐请参考前面。

问题

  1. 为什么实际内存对齐才用8字节?
    1. 在iOS对象中,由于任何类都继承NSObject,然后都会有8字节长的isa_t指针,所以根据内存对齐规则,iOS对象才用了8字节对齐。
  2. 为什么系统开辟不采用8字节,而选择16字节?
    1. 内存容错:对于一个没有任何成员变量的对象来说,它的大小是8字节(isa_t指针)。如果多个没有成员变量的对象存在一起,就会出现每个对象isa_ta指针挨着情况,没有容错性。而才用16字节对齐,之间有8字节的容错性,可有利于对象的扩展。
    2. 读取速度快:以16字节的方式去读取,可以加快它的读取速度,提高cpu利用率。

内存优化

  • 由于iOS中对象的本质就是结构体,那么我们可以说对象的内存对齐就是结构体的内存对齐么?我们通过例子具体来探究一下

  • 先定一个Test类,然后Test类里面的成员变量和Struct1保持一致

@interface Test : NSObject

@property (nonatomic, assign) double a;
@property (nonatomic, assign) char b;
@property (nonatomic, assign) int c;
@property (nonatomic, assign) short d;

@end

@implementation Test

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test *test = [Test alloc];
        test.a = 10.0;
        test.b = 'a';
        test.c = 12;
        test.d = 100;
        
        NSLog(@"struct1 = %lu Test = %lu",sizeof(struct1), class_getInstanceSize([test class]));
    }
    return 0;
}

打印结果:内存对齐[42277:218736] struct1 = 24 Test = 24

  • Test类中定义的变量和struct2中顺序是一样的,但是test对象会多带一个isa_t变量,该变量占8个字节。打印结果显示它们的大小是一样的,说明test对象我们定义的变量直占了16个字节。明明和结构体顺序、类型都一样的,为什么对象占用的内存和结构体却不一样。这是因为苹果的内存优化机制

  • 我们具体看一下它是如何进行内存优化的:

  • 通过lldb断点打印可以看出,a的读取是通过0x4024000000000000b的读取是通过0x0061c的读取是通过0x0000000cd的读取是通过0x0064。我们可以发现char b; int c; short d;共用了一个8字节空间。
  • 苹果采用时间换空间的方式,通过对对象的属性存储顺序进行重排,达到内存优化的目的。

总结

  • 为了提高cpu的存储效率和安全访问,制定了内存对齐规则。但是也因为内存对齐,从而浪费了很多内存。苹果为了内存优化尽可能降低内存的浪费,使用了属性重排的方式。
  • 既保证了存储的效率,又减少了内存的浪费。

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

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

相关文章

基于C++的AGV机器人无线控制

1 AGV系统概述 1.1AGV原理 AGV行走控制系统由控制面板、导向传感器、方向电位器、状态指示灯、避障传感器、光电控制信号传感器、驱动单元、导引磁条、电源组成。 AGV的导引&#xff08;Guidance&#xff09;是指根据AGV导向传感器&#xff08;Navigation&#xff09;所得到…

基于FFmpeg进行rtsp推流及拉流(详细教程)

目录 1.1 Windows系统 1.2 Ubuntu 和 Debian 系统 1.3 CentOS 和 Fedora 系统 1.4 macOS系统 2. 安装rtsp-simple-server 3. FFmpeg推流 3.1 UDP推流 3.2 TCP推流 3.3 循环推流 4 拉流 4.1 ffplay/VLC拉流显示 4.2 FFmpeg拉流保存成视频 1. 安装FFmpeg FFmpeg 是一…

tftp服务/nfs服务/二进制工具集/uboot基础

一、什么是系统移植 1&#xff09;系统移植就是给开发板搭建一个linux操作系统 2&#xff09;从官方获取源码&#xff0c;进行配置和编译&#xff0c;生成板子需要的镜像文件 二、为什么系统移植 1&#xff09;为后面学习驱动开发课程打基础 2&#xff09;驱动开发工程师必…

入行4年,跳槽2次,在软件测试这一行我已经悟了!

近年来&#xff0c;软件测试行业如火如荼。互联网及许多传统公司对于软件测试人员的需求缺口逐年增大。然而&#xff0c;20年的疫情导致大规模裁员&#xff0c;让人觉得行业寒冬已经到来。软件测试人员的职业规划值得我们深思。 大家对软件测试行业比较看好&#xff0c;只是因…

【云服务器 ECS 实战】专有网络 VPC、弹性网卡的概述与配置

一、ECS 专有网络 VPC1. 传统经典网络与专有网络 VPC 对比2. 建立自己的专有网络 VPC二、弹性网卡1. 弹性网卡的概念与优势2. 弹性网卡的配置一、ECS 专有网络 VPC 阿里云在早期使用的是一种传统的网络模式&#xff0c;将所有的 ECS 云服务直接建立在传统网络层之上&#xff0…

【有营养的算法笔记】归并排序

&#x1f451;作者主页&#xff1a;进击的安度因 &#x1f3e0;学习社区&#xff1a;进击的安度因&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;有营养的算法笔记 文章目录一、思路二、模板讲解三、模板测试四、加练 —— 逆序对的数量今天讲解的内容是…

[附源码]Python计算机毕业设计SSM加油站管理信息系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【论文笔记】InverseForm: A Loss Function for Structured Boundary-Aware Segmentation

论文 标题&#xff1a;InverseForm: A Loss Function for Structured Boundary-Aware Segmentation 收录于&#xff1a;CVPR 2021 论文&#xff1a;[2104.02745] InverseForm: A Loss Function for Structured Boundary-Aware Segmentation (arxiv.org) 代码&#xff1a;Git…

大数据都应用在哪些领域?

大数据被应用较多的领域有哪些&#xff1f;疫情期间大数据技术对于疫情的防控发挥了巨大的作用&#xff0c;抗疫期间多家互联网企业纷纷加强大数据在疫情防控中的应用。小到社区大到部委相关部门都将大数据作为不可或缺的防疫工具&#xff0c;生活中很多方面涉及到大数据由此可…

Scala014--Scala中的函数

一&#xff0c;函数的定义和声明 对于其他计算机语言来说&#xff0c;如Java&#xff0c;python&#xff0c;函数和方法是一样的&#xff0c;但是对于Scala来说&#xff0c;函数和方法并不是同一个概念&#xff0c;方法是类或者是对象的成员&#xff0c;而函数是一个对象。但是…

澳亚集团通过聆讯:毛利率波动,预计利润将下滑,陈荣南为董事长

撰稿|汤汤 来源|贝多财经 近日&#xff0c;港交所披露的信息显示&#xff0c;澳亚集团有限公司&#xff08;下称“澳亚集团”&#xff09;通过港交所聆讯&#xff0c;并披露了聆讯后资料集&#xff08;即招股书&#xff09;&#xff0c;中金公司和星展银行&#xff08;DBS&am…

如何从 Power BI 示例中获取数据以供练习

如果您是 Power BI 初学者, Microsoft Power BI 教程中提供的示例是入门的好地方。 在这篇文章中,我将按照步骤在 excel 中查看示例数据,以便您可以将这些数据用于练习目的。 下载 Excel 文件 首先,在浏览器中打开人力资源数据。文包含有关如何使用数据构建 Power BI 报…

不是我穷,是他真的很好用!

今天猫猫为您推荐一款良心vx小程序——喵盐配音&#xff01;利用小程序的特性&#xff0c;无需安装&#xff0c;即走即用。 这个配音软件超多功能&#xff0c;太强大了&#xff01;操作简单&#xff0c;输入文本&#xff0c;一键配音&#xff0c;小白也会用超多声音主播&#…

12月8日绿健简报,星期四,农历十一月十五

12月8日绿健简报&#xff0c;星期四&#xff0c;农历十一月十五1. 中国铁路&#xff1a;即日起购票乘车及进出站停止查验核酸和健康码&#xff1b;联防联控机制&#xff1a;不再对跨地区流动人员查验健康码&#xff0c;无症状和轻型病例一般采取居家隔离。2. 包头&#xff1a;交…

torchnet 简单使用文档

torchnet 是用于 torch 的代码复用和模块化编程的框架&#xff1a; 主要包含4个部分&#xff1a; Dataset&#xff1a;各种不同方式处理数据。Engine:各种机器学习算法Meter:性能度量指标。 Log&#xff1a;Log&#xff1a;Log&#xff1a; 模块详细分为如下部分&#xff1a;D…

FLStudio2023电脑版安装下载及fl21版本新功能介绍

FL Studio水果简称FL&#xff0c;全称&#xff1a;Fruity Loops Studio&#xff0c;国人习惯叫它"水果"。软件现有版本是 FL Studio 21&#xff0c;已全面升级支持简体中文语言界面 。FL Studio 能让你的计算机就像是全功能的录音室一样&#xff0c;完成编曲、剪辑、…

YourKit Profiler for .NET功能和内存一体探查器

YourKit Profiler for .NET功能和内存一体探查器 Windows和Linux的简单易用内存和性能.NET探查器。 功能和内存一体的.NET探查器 远程和本地评测各种.NET Center和.NET设计应用程序、服务器和Windows服务。 探索测试、开发和制造环境中的性能问题。 与Visual Studio和JetBrains…

一键式开启:IDaaS 日志接入 SLS日志审计发布

背景 什么是IDaaS 应用身份服务IDaaS(Identity as a Service)是阿里云原生身份管理系统&#xff0c;可以统一管理各应用中分散的账号&#xff0c;并集中分配应用访问控制权限&#xff0c;降低低效、重复的账号访问配置和运维工作。IDaaS 旗下的EIAM&#xff08;Enterprise IA…

java计算机毕业设计ssm乡村疫情防控管理系统37804(附源码、数据库)

java计算机毕业设计ssm乡村疫情防控管理系统37804&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#…

时间都去哪了?

在很长一段时间我并不知道怎么去平衡速率和质量之间的关系&#xff0c;我虽然看过不少书和文章告诉我只有保证质量才能保证速率&#xff0c;但我还没有见过反例&#xff0c;我没办法很好地说服别人&#xff0c;我只能看着他们义无反顾的冲向进度&#xff0c;然后抱怨时间不够。…