golang 全局变量_Golang 性能优化技巧—基础编码原则

Go语言中文网,致力于每日分享编码知识,欢迎关注我,会有意想不到的收获!

d0352b8df699a5af8c1e3df6098b8e4a.png

前言

  • 高级设计。为遇到的问题选择适当的算法和数据结构。要特别警觉,避免使用那些会渐进地产生糟糕性能的算法或编码技术
  • 基本编码原则。避免限制优化的因素,这样编译器就能产生高效的代码。
  1. 消除连续的函数调用。在可能时,将计算移到外循环中。考虑有选择地妥协程序的模块性以获得更大的效率。
  2. 消除不必要的存储器引用。引入临时变量来保存中间结果。只有在最后的值计算出来时,才将结果存放在到数组或全局变量中。
  • 低级优化
  1. 展开循环,降低开销,并且使得进一步的优化成为可能。
  2. 通过使用例如多个累积变量和重新结合等技术,找到方法提高指令级并行。
  3. 用功能的风格重写条件操作,使得编译采用条件数据传送。

这段话摘自《深入计算机系统原理》一书中,讲的是三3种性能优化方案,针对程序不同层次来提升性能。

高级设计:指的是程序整体的设计,采用适当的算法和数据结构。

基本编码原则:从指令的角度考虑,开发中应如何编码,才能减少执行的指令。

低级优化:针对现代处理器,如何让cpu的流水线尽量饱和。

本文主要只对基本编码原则进行说明,通过一个例子说明其编码的原因,由浅入深。给出的例子是用golang代码编码,虽然《深入理解计算机系统》一书中是用C语言来讲解,但对于也是转成化成机器码的golang来说都是差不多。编码器在转换成机器码的过程中能帮助开发者是有限的,性能的提升更多是需要依赖程序员的设计。

e819eaf4f16de2dc49e32fbc01c9282f.png

分析

先看一下我们给出的例子,代码如下:

6fca4b94e53f6c688596edfca1348b30.png

此处我们建立一个比较大的切片数组来做求和运算,之所以选用比较大的测试数据,是为了减少运行中其它因素的干扰影响,达到更明显的对比效果。下面是最开始的求和代码:

8f5e825db607daf36085dbdcef5f1491.png

此求和是将整个数组的所有元素加到result指针上,代码很简洁,但是就是这样的代码有着非常大的优化空间。我们先按从"基本编码原则"里的第一个点分析。

1.消除连续的函数调用

首先是GetDataLen()这句代码并不需要被反复调用,但它现在放在for循环当中。由于调用一个函数,处理器执行会增加一定的延迟,这中间需要过程压栈,更改程序计数器,再加上内部调用len方法也需要一定的消耗。只是调用几次性能上并不会有大的损失,但是成千上百万次的话,性能的差异就明显了。因此以下是对toSum1()函数的改进:

bd82ae608d18e32d0547d7854396820d.png

toSum2函数,内部定义一个局部变量dataLength来保存长度,从而减少了GetDataLen()的调用。接着通过以下性能测试,来对两次改进进行性能对比,来查看其性能变化。

17c6e04e5aa464dfe623981e9b9f4155.png

执行查看其结果

79b9653a3c41dfbd8e50cc1f725ed173.png

改进后快了将近20s。这里需要注意的是不同的设备测试结果是不一样的,此数据仅供参考,仅证明该改进是有明显的性能提升的。

其次是第二个方法 GetValue(i)的调用,凡是函数或方法的调用就会有额外的损耗(压栈、修改计数器等),虽然对处理器来说这个没算什么,毕竟cpu是以ns为执行单位,但还是对该函数再进一步改造。

b07d3d3fa2f5aa50e907575ca7bf9c3e.png

新增加了一个函数GetData()用于获取整个切片,不再调用GetValue()。再执行同样的测试得到以下的数据

2511a2ad6c1f4b4473b57824407ca264.png

对比toSum2()的测试数据,只是提升了几秒的效率,原因是这次减少的是函数的调用过程,而对切片内容的访问还是需要的,所以看到的效果不是很明显。(注:此次的改动虽然提高了性能,但考虑到如果开发者不希望知道内部数据结构,那该改动影响对该数据内容的抽象。)

bd3a4172e8e394e3dc6282443b99d0c5.png

2.消除不必要的存储器引用

*result += data[i] 这段代码我们首先要明白处理器指行它的时候要经过什么步骤。它主要的过程需要以下几步:

​ 1.通过result地址值,从内存取出数据放在寄存器中(假设寄存器A)

​ 2.再通过切片数组的首地址获取第i个元素到寄存器中(假设寄存器B)

​ 3.接着将寄存器B 加到寄存器 A中

​ 4.最后再将寄存A写回 result 指向的内存地址

由于*result在这过程中需要反复地读写,是没有必要的操作,因此我们将再对它做以下的改动。

8206e04e3202b6638a9f225f620ad0dd.png

此处的执行将原先放置在 result 位置的复制到局部变量k中,到最后再将k的值写回result位置中。此处相当是将*result保到固定的寄存器中,让其一直被用作求和运算。此处改动,就相当于节省原有4个步聚里面的1、4步聚,变成以下两个:

​ 1.通过切片数组的首地址获取第i个元素到寄存器中(假设寄存器B)

​ 2.接着将寄存器B 加到寄存器 A中(假设 k 指向的是寄存器A)

再看看其性能对比:

836c728b96fbe3bdd4d81f27acb5a8d1.png

对比 toSum3,又减少了10秒,效果很明显。虽然这里只是用指针做例子,但数组和结构体也是一样的,对内部变量的访问是同指针类似。(注:结构体是通过首地址计算再去内存中获取对应的变量值)

总结

此处给出的性能提升只是从指令的角度考虑,并在这过程演示了《深入理解计算机系统》书中所讲的基本编码原则所带来的效益。而如果想要对该函数有更大的提升空间,我们还可以从"低级忧化"忧化入手,在本文中就暂不讲解,后续有时间再对其做补充。废话也不多说,为本文总结一句:消除连续的函数调用和不必要的存储器引用

测试代码

https://github.com/wpnine/PerformanceExample

作者:wp_nine

链接:https://www.jianshu.com/p/0dafe1059fdc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Go语言中,全局变量的作用域是整个包,其他包无法直接引用main包中定义全局变量。为了解决这个问题,可以采用以下两种方法: 1. 定义并编写单独的全局变量包:创建一个独立的包,用于存放全局变量。在该包中定义全局变量,并在其他包中引入该包来使用全局变量。 ```go package global var Gvar int ``` 在其他包中引入并使用该全局变量包: ```go package test import ( "fmt" "app/global" ) func test() { var err error global.Gvar = 2 fmt.Println(global.Gvar) } ``` ```go package main import ( "fmt" "app/global" ) var Gvar int func main() { var err error global.Gvar = 1 fmt.Println(global.Gvar) } ``` 2. 局部变量全局变量同名:在Go语言中,全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。 ```go package main import "fmt" var quanju = 10 // 全局变量 func main() { var quanju = 100 // 局部变量 fmt.Println("quanju=", quanju) // 输出局部变量的值 } ``` 总结:在Go语言中,全局变量的作用域是整个包,其他包无法直接引用main包中定义全局变量。可以通过定义单独的全局变量包或使用同名的局部变量来解决这个问题。 #### 引用[.reference_title] - *1* [golang关于全局变量的使用和理解](https://blog.csdn.net/itopit/article/details/127469006)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Golang开发--全局变量/局部变量](https://blog.csdn.net/liulanba/article/details/115769826)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值