Golang如何编写内存高效及CPU调优的Go结构体

目录
  • 前言
  • 输出结果
  • 输出结果

前言

结构体是包含多个字段的集合类型,用于将数据组合为记录。这样可以将与同一实体相关联的数据利落地封装到一个轻量的类型定义中,然后通过对该结构体类型定义方法来实现不同的行为。

本文会尝试从内存利用CPU周期的角度讲解如何高效编写struct

我们来看下面这一结构体,这是我们一个奇怪用例所定义的terraform资源类型:

type TerraformResource struct {
  Cloud                string                       // 16字节
  Name                 string                       // 16字节
  HaveDSL              bool                         //  1字节
  PluginVersion        string                       // 16字节
  IsVersionControlled  bool                         //  1字节
  TerraformVersion     string                       // 16字节
  ModuleVersionMajor   int32                        //  4字节
}

使用如下代码来了解TerraformResource结构体需要分配多少内存:

package main
import (
	"fmt"
	"unsafe"
)
type TerraformResource struct {
	Cloud               string // 16字节
	Name                string // 16字节
	HaveDSL             bool   //  1字节
	PluginVersion       string // 16字节
	IsVersionControlled bool   //  1字节
	TerraformVersion    string // 16字节
	ModuleVersionMajor  int32  //  4字节
}
func main() {
	var d TerraformResource
	d.Cloud = "aws"
	d.Name = "ec2"
	d.HaveDSL = true
	d.PluginVersion = "3.64"
	d.TerraformVersion = "1.1"
	d.ModuleVersionMajor = 1
	d.IsVersionControlled = true
	fmt.Println("==============================================================")
	fmt.Printf("结构体使用的总内存:d %T => [%d]\n", d, unsafe.Sizeof(d))
	fmt.Println("==============================================================")
	fmt.Printf("结构体中的Cloud字段:d.Cloud %T => [%d]\n", d.Cloud, unsafe.Sizeof(d.Cloud))
	fmt.Printf("结构体中的Name字段:d.Name %T => [%d]\n", d.Name, unsafe.Sizeof(d.Name))
	fmt.Printf("结构体中的HaveDSL字段:d.HaveDSL %T => [%d]\n", d.HaveDSL, unsafe.Sizeof(d.HaveDSL))
	fmt.Printf("结构体中的PluginVersion字段:d.PluginVersion %T => [%d]\n", d.PluginVersion, unsafe.Sizeof(d.PluginVersion))
	fmt.Printf("结构体中的ModuleVersionMajor字段:d.IsVersionControlled %T => [%d]\n", d.IsVersionControlled, unsafe.Sizeof(d.IsVersionControlled))
	fmt.Printf("结构体中的TerraformVersion字段:d.TerraformVersion %T => [%d]\n", d.TerraformVersion, unsafe.Sizeof(d.TerraformVersion))
	fmt.Printf("结构体中的ModuleVersionMajor字段:d.ModuleVersionMajor %T => [%d]\n", d.ModuleVersionMajor, unsafe.Sizeof(d.ModuleVersionMajor))
}

输出结果

$ go run golang-struct-memory-allocation.go 
==============================================================
结构体使用的总内存:d main.TerraformResource => [88]
==============================================================
结构体中的Cloud字段:d.Cloud string => [16]
结构体中的Name字段:d.Name string => [16]
结构体中的HaveDSL字段:d.HaveDSL bool => [1]
结构体中的PluginVersion字段:d.PluginVersion string => [16]
结构体中的ModuleVersionMajor字段:d.IsVersionControlled bool => [1]
结构体中的TerraformVersion字段:d.TerraformVersion string => [16]
结构体中的ModuleVersionMajor字段:d.ModuleVersionMajor int32 => [4]

因此结构体TerraformResource所需分配的总内存是88字节TerraformResource类型内存分配

如下图所示:

为什么是88字节呢?16 +16 + 1 + 16 + 1+ 16 + 4 = 70 bytes,多出来的18字节是从哪来的?

涉及到结构体的内存分配时,总是会分配连续、字节对齐的内存志,字段按所定义的顺序进行内存分配和存储。这里的字节对齐表示连续的内存块按平台的字大小进行偏移排列。

可以很清楚地看到TerraformResource.HaveDSLTerraformResource.isVersionControlledTerraformResource.ModuleVersionMajor分别仅占用1字节1字节4字节。剩余的空间使用空白字节进行填充。

所以重新计算一下:

数据占用字节 = 16字节 + 16字节 + 1字节 + 16字节 + 1字节 + 16字节 + 4字节 = 70字节

空白字节 = 7字节 + 7字节 + 4字节 = 18字节

总字节数 = 数据占用字节 + 空白字节 = 70字节 + 18字节 = 88字节

那如何修复这个问题呢?通过恰当地的数据结构对齐我们可以这样来定义结构体:

type TerraformResource struct {
	Cloud               string // 16字节
	Name                string // 16字节
	PluginVersion       string // 16字节
	TerraformVersion    string // 16字节
	ModuleVersionMajor  int32  //  4字节
	HaveDSL             bool   //  1字节
	IsVersionControlled bool   //  1字节
}

使用优化后的结构体来运行同一段代码:

输出结果

$ go run golang-struct-memory-allocation.go 
==============================================================
结构体使用的总内存:d main.TerraformResource => [72]
==============================================================
结构体中的Cloud字段:d.Cloud string => [16]
结构体中的Name字段:d.Name string => [16]
结构体中的HaveDSL字段:d.HaveDSL bool => [1]
结构体中的PluginVersion字段:d.PluginVersion string => [16]
结构体中的ModuleVersionMajor字段:d.IsVersionControlled bool => [1]
结构体中的TerraformVersion字段:d.TerraformVersion string => [16]
结构体中的ModuleVersionMajor字段:d.ModuleVersionMajor int32 => [4]

现在TerraformResource类型总的内存占用72字节

我们来看下在内存中是如何排列的:

仅仅是通过对结构体元素进行了一轮数据结构对齐我们就将所占用的内存由88字节降到了72字节,真是太棒了!!!

我们再来算一下

数据占用字节 = 16字节 + 16字节 + 16字节 + 16字节 +4字节 + 1 byte + 1字节 = 70字节

空白字节 = 2字节

总字节数 = 数据占用字节 + 空白字节 = 70字节 + 2字节 = 72字节

通过恰当的数据结构对齐不仅优化了内存占用,还优化了CPU读取周期,怎么做到的呢?

CPU以为单位从内存中进行读取,一个在32位系统中占用4字节、64位系统中占用8字节。我们声明的第一个结构体类型TerraformResourceCPU需要读取11个字才能读完:

但对优化后的结构体只需要读取9个字:

通过恰当地对结构体进行数据结构排序我们可以让内存分配CPU 读取都变得高效。

到此这篇关于Golang如何编写内存高效及CPU调优的Go结构体的文章就介绍到这了,更多相关Go CPU调优内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • golang中定时器cpu使用率高的现象详析

    前言: 废话少说,上线一个用golang写的高频的任务派发系统,上线跑着很稳定,但有个缺点就是当没有任务的时候,cpu的消耗也在几个百分点. 平均值在3%左右的cpu使用率.你没有任务的时候,cpu还跑到3%,这个说不过去呀.通过查看进程pidstat捕获得知,system系统的cpu消耗也不少. sys的cpu占用率高一般是由于大量的syscall系统调用引起的-. 下面的截图是用strace统计出来的系统调用-. 我们发现  futex 和 pselect6 的syscall非常的多-. 

  • golang通过node_exporter监控GPU及cpu频率、温度的代码

    导语:通过node_exporter监控GPU以及cpu频率.温度,不想用一个node_exporter再加一个dcgm,分开监控.我这里监控的是热区的温度.如果需要监控各个cpu核心的温度需要修改一下代码. 结合了https://gitee.com/kevinliu_CQ/node_exporter监控GPU的代码. 加入了cpu的2项自定义监控https://gitee.com/jiaminxu/self_node_exporter 安装一下go wget https://dl.google

  • Golang如何编写内存高效及CPU调优的Go结构体

    目录 前言 输出结果 输出结果 前言 结构体是包含多个字段的集合类型,用于将数据组合为记录.这样可以将与同一实体相关联的数据利落地封装到一个轻量的类型定义中,然后通过对该结构体类型定义方法来实现不同的行为. 本文会尝试从内存利用和CPU周期的角度讲解如何高效编写struct. 我们来看下面这一结构体,这是我们一个奇怪用例所定义的terraform资源类型: type TerraformResource struct { Cloud string // 16字节 Name string // 16

  • 浅析Golang中的内存逃逸

    目录 什么是内存逃逸分析 为什么需要逃逸分析 如果变量放错了位置会怎样 内存逃逸场景 return 局部变量的指针 interface{} 动态类型 栈空间不足 闭包 性能 最后 什么是内存逃逸分析 内存逃逸分析是go的编译器在编译期间,根据变量的类型和作用域,确定变量是堆上还是栈上 简单说就是编译器在编译期间,对代码进行分析,确定变量分配内存的位置.如果变量需要分配在堆上,则称作内存逃逸了. 为什么需要逃逸分析 因为go语言是自动自动内存管理的,也就是有GC的.开发者在写代码的时候不需要关心考

  • 如何用Node.js编写内存效率高的应用程序

    前言 软件应用程序在计算机的主存储器中运行,我们称之为随机存取存储器(RAM).JavaScript,尤其是 Nodejs(服务端js)允许我们为终端用户编写从小型到大型的软件项目.处理程序的内存总是一个棘手的问题,因为糟糕的实现可能会阻塞在给定服务器或系统上运行的所有其他应用程序.C 和 C++程序员确实关心内存管理,因为隐藏在代码的每个角落都有可能出现可怕的内存泄漏.但是对于 JS 开发者来说,你真的有关心过这个问题吗? 由于 JS 开发人员通常在专用的高容量服务器上进行 web 服务器编程

  • 一文搞懂Golang中的内存逃逸

    目录 前言 什么是内存逃逸 查看对象是否发生逃逸 内存逃逸分析的意义 怎么避免内存逃逸 小结 前言 我们都知道go语言中内存管理工作都是由Go在底层完成的,这样我们可以不用过多的关注底层的内存问题,有更多的精力去关注业务逻辑, 但掌握内存的管理,理解内存分配机制,可以让你写出更高效的代码,本文主要总结一下 Golang内存逃逸分析,需要的朋友可以参考以下内容,希望对大家有帮助. 什么是内存逃逸 在了解什么是内存逃逸之前,我们先来了解两个概念,栈内存和堆内存. 堆内存(Heap):一般来讲是人为手

  • golang容易导致内存泄漏的6种情况汇总

    目录 1. 定时器使用不当 1.1 time.After()的使用 1.2 time.NewTicker资源未及时释放 2. select阻塞 2.1 导致goroutine阻塞的情况 2.2 循环空转导致CPU暴涨 3. channel阻塞 4. goroutine导致的内存泄漏 4.1 申请过多的goroutine 4.2 goroutine阻塞 4.2.1 I/O问题 4.2.2 互斥锁未释放 4.2.3 死锁 4.2.4 waitgroup使用不当 5. slice 引起的内存泄漏 6.

  • jmeter在linux系统下运行及本地内存调优的方法详解

    1.在linux系统下安装跨系统传输文件工具 root用户下 根目录输入 yum -y install lrzsz 2.把apache-jmeter-4.0zip包 用rz命令上传到linux系统的根目录下 解压 3.配置jmeter环境变量 vim /etc/profile 添加 export PATH=/apache-jmeter-4.0/bin/:$PATH 注意路径 4.使用 rz命令上传jdk1.8 linux 64位版本 解压到 usr/local 目录下 下载jdk安装包 下载地址

  • AngularJS进行性能调优的7个建议

    AnglarJS作为一款优秀的Web框架,可大大简化前端开发的负担.近日Sebastian Fröstl在一篇博文<AngularJS Performance Tuning for Long Lists>中表示AnglarJS在处理包含复杂数据结构的大型列表时,其运行速度会非常慢.他在文中同时分享了解决方案.下面为该文的译文. AnglarJS很棒,但当处理包含复杂数据结构的大型列表时,其运行速度就会非常慢.这是我们将核心管理页面迁移到AngularJS过程中遇到的问题.这些页面在显示500行

  • Logback与Log4j2日志框架性能对比与调优方式

    目录 前言 性能测试 logback 同步日志 异步日志(队列扩容) 异步日志(半队列扩容) log4j2 同步日志 异步日志(队列扩容) 异步日志(日志淘汰策略) 异步日志(半队列扩容) 异步日志(等待策略) 性能调优 异步日志 日志可靠性 Logback Log4j2 日志抛弃策略 Log4j2 Logback 日志等待策略 TimeoutWaitStrategy YieldWaitStrategy 队列容量 Logback Log4j2 长度计算公式 消费瓶颈 消费TPS 请求TPS 消费

  • jvm垃圾回收之GC调优工具分析详解

    进行GC性能调优时, 需要明确了解, 当前的GC行为对系统和用户有多大的影响.有多种监控GC的工具和方法, 本章将逐一介绍常用的工具. JVM 在程序执行的过程中, 提供了GC行为的原生数据.那么, 我们就可以利用这些原生数据来生成各种报告.原生数据(raw data) 包括: 各个内存池的当前使用情况, 各个内存池的总容量, 每次GC暂停的持续时间, GC暂停在各个阶段的持续时间. 可以通过这些数据算出各种指标, 例如: 程序的内存分配率, 提升率等等.本章主要介绍如何获取原生数据. 后续的章

随机推荐