new 结构体指针_Go:我应该用指针替代结构体的副本吗?

16b70bb32df9ab876590769e910de7f0.png

logo

对于许多 golang 开发者来说,考虑到性能,最佳实践是系统地使用指针而非结构体副本。

我们将回顾两个用例,来理解使用指针而非结构体副本的影响。

1. 数据分配密集型

让我们举一个简单的例子,说明何时要为使用值而共享结构体:

type S struct {   a, b, c int64   d, e, f string   g, h, i float64}

这是一个可以由副本或指针共享的基本结构体:

func byCopy() S {   return S{      a: 1, b: 1, c: 1,      e: "foo", f: "foo",      g: 1.0, h: 1.0, i: 1.0,   }}func byPointer() *S {   return &S{      a: 1, b: 1, c: 1,      e: "foo", f: "foo",      g: 1.0, h: 1.0, i: 1.0,   }}

基于这两种方法,我们现在可以编写两个基准测试,其中一个是通过副本传递结构体的:

func BenchmarkMemoryStack(b *testing.B) {   var s S   f, err := os.Create("stack.out")   if err != nil {      panic(err)   }   defer f.Close()   err = trace.Start(f)   if err != nil {      panic(err)   }   for i := 0; i < b.N; i++ {      s = byCopy()   }   trace.Stop()   b.StopTimer()   _ = fmt.Sprintf("%v", s.a)}

另一个非常相似,它通过指针传递:

func BenchmarkMemoryHeap(b *testing.B) {   var s *S   f, err := os.Create("heap.out")   if err != nil {      panic(err)   }   defer f.Close()   err = trace.Start(f)   if err != nil {      panic(err)   }   for i := 0; i < b.N; i++ {      s = byPointer()   }   trace.Stop()   b.StopTimer()   _ = fmt.Sprintf("%v", s.a)}

让我们运行基准测试:

go test ./... -bench=BenchmarkMemoryHeap -benchmem -run=^$ -count=10 > head.txt && benchstat head.txtgo test ./... -bench=BenchmarkMemoryStack -benchmem -run=^$ -count=10 > stack.txt && benchstat stack.txt

以下是统计数据:

name          time/opMemoryHeap-4  75.0ns ± 5%name          alloc/opMemoryHeap-4   96.0B ± 0%name          allocs/opMemoryHeap-4    1.00 ± 0%------------------name           time/opMemoryStack-4  8.93ns ± 4%name           alloc/opMemoryStack-4   0.00Bname           allocs/opMemoryStack-4    0.00

在这里,使用结构体副本比指针快 8 倍。

为了理解原因,让我们看看追踪生成的图表:

4a53e10fd49c91d3b32a53a206b29769.png
b1a86d9b88aee64a4096857b2c1f96ea.png

第一张图非常简单。由于没有使用堆,因此没有垃圾收集器,也没有额外的 goroutine。对于第二张图,使用指针迫使 go 编译器将变量逃逸到堆[1],由此增大了垃圾回收器的压力。如果我们放大图表,我们可以看到,垃圾回收器占据了进程的重要部分。

f1376dfcd1890c1af386da7bf49e6aff.png

在这张图中,我们可以看到,垃圾回收器每隔 4ms 必须工作一次。如果我们再次缩放,我们可以详细了解正在发生的事情:

18ee1b6ac9b5b18aa74c8ecadc682e45.png

蓝色,粉色和红色是垃圾收集器的不同阶段,而棕色的是与堆上的分配相关(在图上标有 “runtime.bgsweep”):

清扫是指回收与堆内存中未标记为使用中的值相关联的内存。当应用程序 Goroutines尝试在堆内存中分配新值时,会触发此活动。清扫的延迟被添加到在堆内存中执行分配的成本中,并且与垃圾收集相关的任何延迟没有关系。

Go 中的垃圾回收:第一部分 - 基础[2]

即使这个例子有点极端,我们也可以看到,与栈相比,在堆上为变量分配内存是多么消耗资源。在我们的示例中,与在堆上分配内存并共享指针相比,代码在栈上分配结构体并复制副本要快得多。

如果你不熟悉堆栈或堆,如果你想更多地了解栈或堆的内部细节,你可以在网上找到很多资源,比如 Paul Gribble 的这篇文章[3]

如果我们使用 GOMAXPROCS = 1 将处理器限制为 1,情况会更糟:

name        time/opMemoryHeap  114ns ± 4%name        alloc/opMemoryHeap  96.0B ± 0%name        allocs/opMemoryHeap   1.00 ± 0%------------------name         time/opMemoryStack  8.77ns ± 5%name         alloc/opMemoryStack   0.00Bname         allocs/opMemoryStack    0.00

如果栈上分配的基准数据不变,则堆上的基准从 75ns/op 降低到 114ns/op。

2.方法调用密集型

对于第二个用例,我们将在结构体中添加两个空方法,稍微调整一下我们的基准测试:

func (s S) stack(s1 S) {}func (s *S) heap(s1 *S) {}

在栈上分配的基准测试将创建一个结构体并通过复制副本传递它:

func BenchmarkMemoryStack(b *testing.B) {   var s S   var s1 S   s = byCopy()   s1 = byCopy()   for i := 0; i < b.N; i++ {      for i := 0; i < 1000000; i++  {         s.stack(s1)      }   }}

堆的基准测试将通过指针传递结构体:

func BenchmarkMemoryHeap(b *testing.B) {   var s *S   var s1 *S   s = byPointer()   s1 = byPointer()   for i := 0; i < b.N; i++ {      for i := 0; i < 1000000; i++ {         s.heap(s1)      }   }}

正如预期的那样,结果现在大不相同:

name          time/opMemoryHeap-4  301µs ± 4%name          alloc/opMemoryHeap-4  0.00Bname          allocs/opMemoryHeap-4   0.00------------------name           time/opMemoryStack-4  595µs ± 2%name           alloc/opMemoryStack-4  0.00Bname           allocs/opMemoryStack-4   0.00

结论

在 go 中使用指针而不是结构体的副本并不总是好事。为了能为你的数据选择好的语义,我强烈建议您阅读 Bill Kennedy[4] 撰写的关于值/指针语义的文章[5]。它将为你提供更好的视角来决定使用自定义类型或内置类型时的策略。

此外,内存使用情况分析肯定会帮助你弄清楚你的内存分配和堆上发生了什么。


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

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

相关文章

localstorage存储大小_Cookie 已凉,Web 存储该这么做!

本文经授权转自公众号CSDN(ID&#xff1a;CSDNnews)作者 | 浪里行舟责编 | 郭芮随着移动网络的发展与演化&#xff0c;我们手机上现在除了有原生 App&#xff0c;还能跑“WebApp”——它即开即用&#xff0c;用完即走。一个优秀的 WebApp 甚至可以拥有和原生 App 媲美的功能和体…

php获取变量数据类型,php如何确定变量的数据类型

在php中&#xff0c;数据类型有&#xff1a;Boolean 布尔类型、Integer 整型、Float 浮点型、String 字符串、Array 数组、Object 对象、Resource 资源类型、NULL;知道一个数据的类型&#xff0c;能够更加有效地进行代码逻辑处理。1、使用 var_dump() 函数&#xff0c;可以获取…

深入理解java虚拟机 - jvm高级特性与最佳实践(第三版)_JVM虚拟机面试指南:年薪30W以上高薪岗位需求的JVM,你必须要懂!...

JVM的重要性很多人对于为什么要学JVM这个问题&#xff0c;他们的答案都是&#xff1a;因为面试。无论什么级别的Java从业者&#xff0c;JVM都是进阶时必须迈过的坎。不管是工作还是面试中&#xff0c;JVM都是必考题。如果不懂JVM的话&#xff0c;薪酬会非常吃亏。其实学习JVM并…

label居中_表格固定列宽时如何居中?

列宽固定居中的设置的时候&#xff0c;我们通常使用 p{宽度} 来指定固定的列宽&#xff0c;这时单元格会自动换行&#xff0c;换行之后是左对齐的&#xff0c;如何获得居中对齐呢&#xff1f;\begin{tabular}{|p{54pt}l|p{71pt}c|p{71pt}c|}\hline Method& Train set&T…

github新建仓库推送代码教学

之前一直用gitee&#xff0c;准备转到github。因为一步一步尝试。如果是新手或许文章会有帮助 点击 new 创建 拉代码 Idea 打开 复制一个 pom 文件作为 maven 管理 提交代码 不出意外的出意外&#xff0c;报错 点击authorize JetBrains 失败 分析问题 本质就是没有…

Linux数码管和点阵程序,随笔:python turtle绘制八段数码管和共阳极8x8led点阵

为更新而更新&#xff0c;为保持更新状态而更新。给学生讲解用gpiozero库控制八段管和8x8共阳极LED点阵。已经讲解了单个LED的控制&#xff0c;RGB彩色灯珠的控制&#xff0c;在讲解八段管就很容易理解&#xff0c;多个八段管的讲解稍微麻烦一点&#xff0c;然后LED点阵为了便于…

一个公网ip多少钱_一个丛书书号多少钱

点击上方“蓝字”&#xff0c;发现更多精彩。联系我们&#xff0c;有惊喜&#xff01;&#xff01;本站点提供&#xff1a;学术出书、自费出书&#xff0c;出版指南攻略、编审润色书稿等服务。如需了解详情&#xff0c;请加责编微信&#xff1a;xueshuzhishi出版出书&#xff0…

嵌入式linux镜像,使用Openembedded定制嵌入式Linux镜像

关键词&#xff1a;ARM,Linux,Openembedded作者&#xff1a;ByToradex秦海摘要&#xff1a;嵌入式设备采用Embedded Linux操作系统进行开发已经越来越成为主流&#xff0c;但是如何将开发完成的Linux uboot/kernel配置&#xff0c;以及应用程序整合到Embedded Linux镜像中以便在…

linux安装 中文乱码怎么解决方法,Linux安装GBK/GB2312程序显示乱码的五种解决方法...

不少用户在Linux系统中安装GBK或GB2312的时候遇到了乱码问题&#xff0c;这主要是系统默认语言是uft8所导致&#xff0c;对于该问题可用五种方法进行解决&#xff0c;接下来是小编为大家收集的Linux安装GBK/GB2312程序显示乱码的五种解决方法&#xff0c;希望能帮到大家。Linux…

linux 设备驱动总结,linux设备驱动归纳总结(三):3面向对象思想和lseek

linux设备驱动归纳总结(三)&#xff1a;3.设备驱动面向对象思想和lseek的实现一、结构体structfile和struct inode在之前写的函数&#xff0c;全部是定义了一些零散的全局变量。有没有办法整合成到一个结构体当中&#xff1f;这样的话&#xff0c;看起来和用起来都比较方便。接…

idea junit 测试看不到控制台报错信息_高手都这么给 Spring MVC 做单元测试!

本章节主要讲解以下两部分内容&#xff1a;1、Mock 测试简介2、测试用例演示一、Mock 测试简介1、什么是 mock 测试在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;用一个「虚拟的对象」来创建以便测试的测试方法&#xff0c;就是 「mock 测试」在…

windows查询每个线程占用的内存_C#多线程

一、基本概念1、进程首先打开任务管理器&#xff0c;查看当前运行的进程&#xff1a;从任务管理器里面可以看到当前所有正在运行的进程。那么究竟什么是进程呢&#xff1f;进程&#xff08;Process&#xff09;是Windows系统中的一个基本概念&#xff0c;它包含着一个运行程序所…

linux 录屏软件 按键,linux下常用的截图、录屏工具

linux下常用的截图、录屏工具(2010-01-05 10:47:21)由于和老公一起做一个百度俱乐部的小项目&#xff0c;在编写测试文档时要使用截图、录屏的小工具&#xff0c;于是展开搜索什么工具比较好使。录屏&#xff1a;在linux下常用的录屏工具有5种&#xff0c;可以baidu或者google下…

实体类blob类型_Mysql的数据类型和JPA的实体类

​MySQL中定义数据字段的类型对你数据库的优化是非常重要的。MySQL支持多种类型&#xff0c;大致可以分为三类&#xff1a;数值、日期/时间和字符串(字符)类型。数值类型Int,4字节,(-2147483648&#xff0c;2147483647), (0,4294967295)BIGINT,8 字节,(-9223372036854775808,92…

python升级版本命令_如何在python中安装和配置kivy库

kivy是python的UI开发工具包&#xff0c;主要关注用户界面显示效果&#xff0c;可以在Android、IOS、Linux、OS X和Windows上运行。如果python开发中使用kivy&#xff0c;需要安装和配置相关文件和依赖模块。1、在电脑左下角打开开始菜单&#xff0c;输入cmd打开终端窗口在电脑…

linux系统如何安装bt5,BT5硬盘安装(多系统linux + win + BT5)

BT5硬盘安装(多系统linux win BT5)实际上&#xff0c;Win BT5的安装不是很困难&#xff0c;但据我所知我也有两种: 1如官方说明中所述&#xff0c;工具unetbootin&#xff0c;运行unetbootin&#xff0c;在“ CD映像”中&#xff0c;选择BT5的ISO&#xff0c;在“类型”中选…

android主板读取vga线数据_智锐通掘金新基建上新系列之3.5quot; 与ATX工业主板图鉴...

4月份&#xff0c;工信部正式公布了《工业互联网体系架构(版本2.0)》&#xff01;5G、大数据、人工智能、区块链、边缘计算等新技术加速融入并不断拓宽工业互联网的内涵与赋能潜力。新一代信息技术为驱动的数字浪潮正在深刻重塑经济社会各个领域的当前&#xff0c;智锐通科技聚…

Python 爬虫之下载视频(五)

爬取第三方网站视频 文章目录 爬取第三方网站视频前言一、基本情况二、基本思路三、代码编写四、注意事项&#xff08;ffmpeg&#xff09;总结 前言 国内主流的视频平台有点难。。。就暂且记录一些三方视频平台的爬取吧。比如下面这个&#xff1a; 一、基本情况 这次爬取的方…

c语言json映射,GitHub - xujun621/cson: 基于C语言的json数据映射解析库

CSON基于cJSON,运行于C语言平台的json-struct模型解析工具简介CSON是一个简单的cJSON的二次封装&#xff0c;相比于使用原生cJSON一层一层解析的方式&#xff0c;CSON采用模型映射的方式&#xff0c;使用模型将结构体的特征进行描述&#xff0c;然后根据模型&#xff0c;将json…

cad渐变线怎么画_怎么画压力线和支撑线

支撑线与压力线的基本画法画法&#xff1a;将两个或两个以上的相对低点连成一条直线即得到支撑线将两个或两个以上的相对高点连成一条直线即得到压力线用法&#xff1a;1.支撑线和压力线的作用支撑线又称为抵抗线。当股价跌到某个价位附近时&#xff0c;股价停止下跌&#xff0…