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,一经查实,立即删除!

相关文章

Oracle 同音字查询,汉字的演变过程100字,汉字的演变图片

中国的汉字博大精深&#xff0c;历史悠久。早期甲骨文主要是线条图&#xff0c;统一而优美。后来&#xff0c;它逐渐演变成一幅由笔画组成的图画&#xff0c;这是当代汉字的基础商周时期&#xff0c;甲骨文逐渐成为金文。西周时期&#xff0c;青铜器铸造&#xff0c;铭文用于记…

python音频聚类_python实现鸢尾花三种聚类算法(K-means,AGNES,DBScan)

python实现鸢尾花三种聚类算法(K-means,AGNES,DBScan)发布时间&#xff1a;2020-08-31 21:23:24

matlab 实验数据 传递函数,《传递函数MATLAB实验》.ppt

传递函数MATLAB实验 练习&#xff1a; 各种典型环节的阶跃响应曲线 1、比例环节(K) 从图形库浏览器中拖曳Step(阶跃输入)、Gain(增益模块)、Scope模块到仿真操作画面&#xff0c;连接成仿真框图。 结论&#xff1a;比例环节 K 改变&#xff0c;则放大倍数改变。 2、积分环节( 1…

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

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

三折线弹塑性滞回模型matlab,动力弹塑性滞回模型-迈达斯汇总.doc

9-1 概要非线性抗震分析方法可分为非线性静力分析方法和非线性动力分析方法。其中非线性静力分析方法(静力弹塑性分析)因其理论概念易于理解、计算效率高、整理结果较为容易等原因为设计人员所广泛使用。但是由于静力弹塑性分析存在反映结构动力特性方面的缺陷、使用的能力谱是…

python实现knn算法鸢尾花_Python学习之knn实现鸢尾花分类

# K近邻算法# 导入相关库文件import numpy as npimport matplotlib.pyplot as plt#import pandas as pdfrom sklearn import neighbors, datasets# 导入数据集&#xff0c;数据集sklearn自带&#xff0c;X与y一一对应dataset datasets.load_iris()# 获取鸢尾花前两列花萼长度和…

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并…

php ajax xmlhttpreq 上传文件 get,使用Ajax XmlHttpRequest上传文件

使用Ajax XmlHttpRequest上传文件嗨&#xff0c;我正在尝试使用此代码发送带有xmlhttprequest的文件。var url "http://localhost:80/....";$(document).ready(function(){document.getElementById(upload).addEventListener(change, function(e) {var file this.fi…

linux 针对目录空间配额,linux磁盘配额quota

Linux是一个多用户多任务的操作系统&#xff0c;在使用中可能会有几个人对服务器有操作&#xff0c;几个用户共同使用一个共享磁盘的情况&#xff0c;因为我们的硬盘是有限的&#xff0c;我们需要对用户的空间进行限制。这里使用磁盘配额&#xff0c;可以很方便的对用户的空间进…

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

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

linux比较两个文件命令cmp,Linux系统中使用cmp和comm命令来比较两个文件

cmpcmp 命令&#xff1a;比较任意两个类型的文件&#xff0c;且吧结果输出到标准输出&#xff0c;默认文件相同不输出&#xff0c;不同的文件输出差异必要参数-c 显示不同的信息-l 列出所有的不同信息-s 错误信息不提示选择参数-i 指定字符数目-v 显示版本信息--help 显示帮助信…

truncate python是删除文件内容吗_Python 文件 truncate() 方法

概述Python 文件 truncate() 方法用于截断文件并返回截断的字节长度。指定长度的话&#xff0c;就从文件的开头开始截断指定长度&#xff0c;其余内容删除&#xff1b;不指定长度的话&#xff0c;就从文件开头开始截断到当前位置&#xff0c;其余内容删除。语法truncate() 方法…

python 内推_[宜配屋]听图阁

本文实例为大家分享了网易有道2017内推编程题&#xff1a;洗牌&#xff0c;供大家参考&#xff0c;具体内容如下[编程题] 洗牌时间限制&#xff1a;1秒空间限制&#xff1a;32768K洗牌在生活中十分常见&#xff0c;现在需要写一个程序模拟洗牌的过程。 现在需要洗2n张牌&#x…

linux根目录cat退出,Linux展示cat帮助信息并退出

Linux显示cat帮助信息并退出Linux显示cat帮助信息并退出youhaidongyouhaidong-ThinkPad-Edge-E545:~$ cat --help用法&#xff1a;cat [选项]... [文件]...将[文件]或标准输入组合输出到标准输出。-A, --show-all 等于-vET-b, --number-nonblank 对非空输出行编号-e 等于-vE-E,…

linux 编译字符设备驱动错误,linux字符设备驱动框架及编写流程

流程&#xff1a;init{}exit{}申请设备号 (动态注册/静态注册) 创建一个字符设备 cdev_alloc初始化字符设备 cdev_init设备号和字符设备关联 cdev_add销毁字符设备 cdev_del解注册设备号 unregister_chrdev_region1 设备号设备号分为主设备号和次设备号主设备号表示一类设备次设…

重新下载python以前下的包还用重新安装吗_强制“pip”在切换到其他Python二进制文件后重新编译以前安装的包(numpy)...

这个问题是关于我的特殊问题(我找到了一个解决方法&#xff0c;所以它不是一个紧迫的问题)的问题&#xff0c;也是关于我正在使用的一般过程的问题。设置(工作部分)&#xff1a;我在我的Ubuntu 14.04上本地安装了Python 2.7.9&#xff0c;还有一个运行它的virtualenv。一切都与…

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点阵为了便于…

linux管理外部工具,linux – 除了iptables之外的数据包管理工具?

我正在寻找可以根据一组规则改变网络数据包的有效内容的linux实用程序.理想情况下,我会使用iptables和netfilter内核模块,但它们不支持通用的有效负载调整&#xff1a;iptables会改变各种头域(地址,端口,TOS等),并且可以匹配数据包中的任意字节,但是它显然无法改变数据包内的任…