GO 内存对齐

前言

之前遇到过这样一个情况(发现问题的结构体并不长这样, 不过为了引出问题, 改了一下):

 
 type Test struct {
 b bool
 i3 int32
 i8 int8
 i64 int64
 by byte
 }
 func main() {
 t := Test{}
 fmt.Printf("%d", unsafe.Sizeof(t))
 }

创建一个结构体, 查看一下其内存占用. 看结果前先简单算一下:

  • bool: 1B
  • int32: 4B
  • int8: 1B
  • int64: 8B
  • byte: 1B

这么算下来的话, Test结构体占用应该是: 1+4+1+8+1=15B. 15个字节对吧. 来, 打印看一下:

32个字节???这不坑我么.内存占用直接多出一倍.

探索

通过查找资料, 发现了这样一个名词: 内存对齐. 什么是内存对齐呢?

简单说, 就是CPU在读取数据的时候, 并不是一个字节一个字节读取的, 而是一块一块读取的. 那么这个快是多大呢? 根据CPU位数不同而不同.

GO编译器在编译的时候, 为了保证内存对齐, 对每一个数据类型都给出了对齐保证, 将未对齐的内存留空. 如果一个类型的对齐保证是4B, 那么其数据存放的起始地址偏移量必是4B 的整数倍. 而编译器给出的这个对齐保证是多少呢? 不同版本不同平台的编译器不尽相同, 可以通过函数unsafe.Alignof 来获取.

通过分析之前的数据结果, 就能大致理解了. 先来看一下几个类型对齐保证的值:

 
 fmt.Printf("bool: %d\n", unsafe.Alignof(bool(false)))
 fmt.Printf("int32: %d\n", unsafe.Alignof(int32(0)))
 fmt.Printf("int8: %d\n", unsafe.Alignof(int8(0)))
 fmt.Printf("int64: %d\n", unsafe.Alignof(int64(0)))
 fmt.Printf("byte: %d\n", unsafe.Alignof(byte(0)))

结果如下:

来尝试一个一个放到内存中(下图中每个空白代表一个字节):

1.放入bool: 其对齐保证为1, 第一个变量, 直接放入即可.

2.放入int32. 其对齐保证为4, 既偏移量为4的整数倍. 而现有地址中, 首个4的整数倍为第四个字节(中间三字节留空).

按照这个思路, 依次将后面的变量放入, 结果占用的内存为(其中字母依次为变量占用, X为对齐留空):

AXXX BBBB CXXX XXXX DDDD DDDD E

但是这才25个字节啊. 和实际的32字节还差点呢. 别急, 再看一下结构体的对齐保证, 发现是8B. 上面不是8B 的整数倍, 往后补零. 结果:

AXXX BBBB CXXX XXXX DDDD DDDD EXXX XXXX

如此一来, 就正好32位了. 结构体的对齐保证, 为其成员变量对齐保证的最大值.

why

那么编译器为什么要做内存对齐这种事情呢? 举个例子, 如果不做内存对齐, 那么下面这个结构体的内存分布为:

 
 type Test struct {
 b bool
 i3 int32
 }

ABBB B

还记得之前说, CPU读取内存是一块一块读取的么? 而这个块, 假设是4B.

这样的话, 当你需要读取i3变量的时候, 需要进行两次内存访问. 而对齐之后, 只需要进行一次内存访问即可. 是典型的空间换时间的做法.

修改

既然知道了问题出在哪里, 那么是不是如果换一下字段的存放顺序, 就可以压缩内存空间了呢? 思路很简单, 将对齐保证小的放到前面, 试一下:

 
 type Test struct {
 b bool
 by byte
 i8 int8
 i3 int32
 i64 int64
 }
  
 func main() {
 t := Test{}
 fmt.Printf("%d", unsafe.Sizeof(t))
 }

通过之前的对齐分析. 结果确为18B. 也就是因为字段顺序的问题, 编译器为了保证内存对齐, 向其中填充了很多空白, 造成了内存的浪费.

仅仅是修改了一下字段的顺序, 就可以将结构体的内存占用直接降低一倍. 见识了...

检测工具

那么, 有没有什么办法能够帮我们检测是否存在内存对齐的优化呢? 毕竟平常写的时候, 谁会关心这玩意呢. 别说, 还真有. golangci-lint

官网: https://golangci-lint.run/

安装: brew install golangci-lint

检测所有文件命令: golangci-lint run ./..

检测一下最开始的结构体文件(添加参数指定检测内存对齐):

golangci-lint run --disable-all -E maligned main.go

看到结果:

会看到提示, 该结构体当前占有32B, 可优化至16B. 完美.

当然, 此工具的功能不仅如此, 它能够提供很多建议, 有待发掘.


其实, 项目中估计也很少有关注内存对齐的时候吧. 不过毕竟积少成多, 内存这玩意, 能省则省嘛.

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

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

相关文章

HBase 命令行

hbase是一款分布式数据库. 其对数据的索引只通过row key进行. 在存储数据的时候, 通过row key的排序进行存储. 在面对一个新的数据库时, 深究其原理并不知一个明智的选择, 正如开车一般, 大多数人都是先学会开车, 然后在开车的过程中车子出故障了, 再慢慢学着去修理. 不管怎么说…

《论可计算数及其在判定上的应用》简单理解

刚刚拜读了一本书, 《图灵的秘密》. 该书介绍了图灵的论文《论可计算数及其在判定上的应用》, 其指出: 一个拥有铅笔, 纸和一串明确指令的人类计算者, 可以被看做是一种图灵机. 那么图灵机是什么呢? 是图灵为了描述可计算数而引出的一个虚构的可以做一些简单操作的计算机器. 尽…

PHP为什么empty可以访问不存在的索引

开始之前, 先抛出问题: $arr []; echo empty: , PHP_EOL; var_dump(empty($arr[1])); echo is_array: , PHP_EOL; var_dump(is_array($arr[1]));这段代码的运行结果: 你是否和我有过同样的疑问? 同样是函数, 为什么empty访问不存在的索引就不会报错呢? 按理说哈, 函数调用的…

浮点数运算丢失精度

今天碰到了这样一个情况, 使我又去翻阅了原来课本, 在Pthon中如果输入下面这段程序: print(sys.float_info.max - 1.0) print(sys.float_info.max)结果如下: 结果发现, 这数字根本没有变化. 本来这没什么, 看这数字, 10的308次方, 也就是说, 减去的1是在308位之后了, 这里没有…

变量的作用域

起因 最近闲来无事, 在 Python 官网上看到了2.0版本, 是2001年的. 打算装起来体验一下最初发布的版本, 但是发现只有 Windows 版本, 所以我就装了个 Windows10的虚拟机, 就在我打算安装的时候, 发现: 这激起了我的好胜欲, 于是我就依次安装了Windows 8, Windows 7, Windows XP…

PHP8的注解

PHP8.0增加了注解的支持, 虽然 PHP的注解没用过, 但是咱用过JAVA的注解呀. 注解这玩意怎么用? 简单说就下面几步: 定义注解类使用注解提取注解 到了PHP中, 也基本上换汤不换药. 使用 定义注解类 #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION)] cl…

目的论浅谈

一些心理学主张, 我们的现在乃至未来, 都是由过去所经历的事情所决定的, 这种思想被称为 “原因论”, 同时也符合佛家的思想. 而相对的另一种解释就是 “目的论”. 举个例子, 如果你在饭店吃饭, 被服务员不小心把汤打翻了, 泼了你一身. 此时, 你愤怒不已, 站起身来破口大骂, 此…

HBase 数据存储结构

在HBase中, 从逻辑上来讲数据大概就长这样: 单从图中的逻辑模型来看, HBase 和 MySQL 的区别就是: 将不同的列归属与同一个列族下支持多版本数据 这看着感觉也没有那么太大的区别呀, 它解决了 MySQL 的那些问题呢? 每一个新事物的出现, 都是为了解决原本存在的问题. 对写入…

HBase 文件合并

HBase在存储时, 使用了LSM树来进行数据存储, 会定期将文件进行合并, 以提升数据的查询效率, LSM树都是这么处理的. 那么到这里就有一个问题了, HBase在进行文件合并的时候, 势必会占用大量 IO, 难道不会对正常的业务产生影响么? 抱着这个疑问, 我去找了找HBase文件合并的方式.…

spark计算操作整理

spark 的计算流程大概如图: 其中, 通过多次处理, 生成多个中间数据, 最后对结果进行操作获得数据. 本文不涉及任何原理, 仅总结spark在处理的时候支持的所有操作, 方便后面使用的时候, 可以参照本文进行数据的处理. 以下函数整理, 基与Python中RDD对象. 数据的转换操作 数据…

软件工程模型

你在工作中, 软件的开发流程是怎样的? 你是否想过, 除了你当前使用的流程, 还存在其他怎样的流程? 现在的流程有哪些问题, 又能够如何解决? 别说, 前辈们已经给出了一些项目流程的模型, 既软件工程. 可以简单了解一下, 带动一下我这生了锈的脑子. 在很久以前, 一个软件的从…

关于对接需求的思考

产品说想要一个登录注册的功能, 你一想, 好说, 不就是用户名密码嘛, 然后开发完成 产品看到成果后: 我要的是手机验证码登录结果写好的功能基本废了 产品又想要一个登录注册的功能, 这回你学乖了, 确认了一下, 是手机验证码登录, 没问题, 然后开发完成 产品拿到成果: 怎么没有验…

路径.git下的文件

用了这么久的git, 可以毫不谦虚地说对git是一无所知. 每天用来用去的就是commit, add, merge 等几个有限的命令, 这不符合我这刨根问底的性格啊. 不行, 得研究研究, 从哪里下手呢? 别的咱先不说, 所有 git 项目都有这么一个文件夹.git, 不如就从它入手 ? 那咱就看看这个文件夹…

git 操作二进制文件

平常用git进行项目管理已经稀松平常了, 今天咱来点不一样的. 平常管理的都是普通的文本文件, 如果是二进制文件, git能够处理么? 比如word文档. 测试一下. 新建一个项目, 在其中创建test1.docx, test2.txt两个空文件并提交. 之后编辑文件并添加标题, git diff看一下效果: 效…

Go 常量定义

定义常量在各个语言中都是不可或缺的语法了. 而有些语言在常量的基础上, 增加了枚举类型, 比如C. enum Weekday {SUNDAY,MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY };上面的枚举, 对应的值依次为0到6. 而在Go中, 是没有提供枚举类型的. 如果实现上面相同的功能, 难…

搭建个人博客

一直都想着搞一个自己的个人博客, 拖着这么久, 最近终于开始动手了. 故留下一篇完整的记录, 若你也刚好有相同的需要, 那这篇文章应该恰好能够帮助到你. 准备 云服务器个人域名 如果没有云服务器, 将应用跑在自己的电脑上, 通过内网穿透大概也能达到效果, 但是个人电脑实在没…

nginx 配置文件的匹配规则

引出 之前在对php-fpm 进行nginx代理时, 为了对后台限定 IP 访问, 添加了如下配置: location ^~ /admin {allow 127.0.0.1;deny all; }结果呢? 所有admin路径下的php文件, 全都没有解析, 变成文件下载了. 当时我不知道是什么问题, 不过将这段配置去掉之后, 问题就消失了. 所…

分库后如何分页

前言 在实际应用中, 为了降低单表的数据量, 会对较大的表进行水平切分, 将单表的数据切分到多表多库中. 既然要切分, 就要有一个切分的依据, 比如说按照 ID 取模等. 那么多张表联合分页是如何做到的呢? 如果分表的依据是字段 A, 但是需要根据字段 B 进行分页查询, 针对这种…

计算机是如何进行时间同步的

WHY 在网络世界中, 各个计算机之间要想协同工作, 时间同步是一个十分重要的基础. 在计算机内部是有自己的时间的, 这个时间通过内部的晶体振荡器差生的固定频率, 来模拟时间流逝进行计算. 虽然频率十分稳定, 但也是有误差的, 虽然现在的工艺水平误差已经十分小了. (关于震荡的…

WordPress架构简单剖析

前言 最近在搭建自己的博客站点时, 选择了网站使用较多的WordPress, 随着慢慢的使用, 它灵活的插件和主题令我折服. 基本上任何想要实现的功能, 都可以在上面通过插件的形式进行添加. 无论是在访问前的缓存、访问后的统计、访问中的过滤、各种流程的修改等等, 几乎都能够以插件…