如何做Go的性能优化?(转)
Go的性能优化其实总的来说和C/C++等这些都差不多,但也有它自己独有的排查方法和陷阱,这些都来源于它的语言特性和环境。
1.性能优化前提——任何好的东西都是在正确的前提上
代码界的很多事是和我们生活的哲学息息相关的,我们想要做好一件事,首先要保证我们能按时完成我们的任务,其次再去想如何把工作做的更好。如果一味只去要求做的尽善尽美可能会导致延期,失败,半途而废。
所以,先写正确的代码,再去考虑如何去让代码更快更好的运行;先完成基本的功能,再去想如何优化它。正确是优化的基础,没有这个基础,任何的优化都是毫无意义的。
2.性能优化限制——架构设计和硬件资源
良好的架构设计是我们能够发挥性能的前提,一个设计不当的架构付出再多精力优化效果也是大打折扣。这也是我们为什么经常会看到随着业务量或者用户数的增加后天架构会不断演进变化,如果说一开始设计的架构可以一直支撑下去,那么请大神请收下我的膝盖!
硬件资源更好理解,一个16核,64G内存的服务器和4核,4G内存的垃圾机器对比简直是天与地。毋庸多说。
3.什么时候做性能优化
We should forget about small efficiencies, say about 97% of the time; premature optimization is the root of all evil(大概97%的时间,我们应该忘记小的优化, 过早优化是所有邪恶的根源). —— Donald E.Knuth
这句话不是说不去优化,不去思考算法,而是在早期我们应该更加专注于程序的实现,而不是一开始就去想着优化,你大可以放开去写。慢慢的会有驱动力让我们不自觉去优化。
正常情况下这种驱动我觉得有两种,一种是自我驱动,比如经历过搞过ACM或者算法竞赛的童鞋们在面对一个问题时会不自觉地从复杂度角度分析问题;或者一个“强迫症患者”不能忍受慢,卡,崩等等情况。
另一种是环境驱动,比如高并发环境,高精度环境,低延迟环境,大数据环境等等对于我们系统的某一方面甚至多个方面都有苛刻的要求,逼这着我们需要不断优(jia)化(ban),优(jia)化(ban),再优(jia)化(ban)。
- 当你意识到这个函数可能会被经常调用,就需要想办法的优化
- 当你意识到这个数据结构设计不合理导致内存占用过高,就需要想办法优化
- 当用户反映服务响应太慢,就需要优化
- 当老板既要好的服务又不想再花钱买机器,就需要优化
- 当代码太乱,问题百出,经常报警告打扰和女票玩耍,就需要优化
- 。。。
4.花多长时间来做性能优化
有人说是二八定律,又名80/20定律、帕累托法则(定律)也叫巴莱特定律、最省力的法则、不平衡原则等,被广泛应用于社会学及企业管理学等。是19世纪末20世纪初意大利经济学家巴莱多发现的。他认为,在任何一组东西中,最重要的只占其中一小部分,约20%,其余80%尽管是多数,却是次要的,因此又称二八定律。—— 百度百科
我觉得虽然我们不必一定按照二比八的要求去执行,但毫无疑问的是优化会耗费我们非常多的时间和精力,并且远远大于我们系统实现的时间,或者说自从第一次开发完,以后所有的时间都是在做优化。自己的曾经的经历,当时为了给某银行做60W终端测试优化一个API缓存系统,基本功能实现两周就完成了,后面我和性能QA童鞋一波波优化——测试——优化——测试,花费了一个多月时间做这件事,这还没完,后面在真实环境测试过程仍然暴露了很多问题,例如goruntine暴增积压,CPU暴增等等,后来发现是架构设计和组件使用上的问题,是的,当出现这样的问题时不是不可以解决,但是为了解决这样的问题会把系统搞的复杂,臃肿,虽然开发经验不多,但我觉得该是代码实现的代码实现,该是组件解决的问题就应该组件来解决,架构设计问题就是架构需要改进,不要说都可以在代码中解决,除非是不得已。
5.工欲善其事,必先利其器
首先是代码层次,好的代码是性能的关键因素,实现函数效率怎么样,排序是不是高效,操作并发性高不高等等,你可以使用代码质量评估工具来做评估,当然最好还是让有经验的司机们手把手指导。
Go代码评估工具:
- goreporter – 生成Go代码质量评估报告
- dingo-hunter – 用于在Go程序中找出deadlocks的静态分析器
- flen – 在Go程序包中获取函数长度信息
- go/ast – Package ast声明了关于Go程序包用于表示语法树的类型
- gocyclo – 在Go源代码中测算cyclomatic函数复杂性
- Go Meta Linter – 同时Go lint工具且工具的输出标准化
- go vet – 检测Go源代码并报告可疑的构造
- ineffassign – 在Go代码中检测无效赋值
- safesql – Golang静态分析工具,防止SQL注入
然后是如何在运行过程来调试Go程序,Go自带了一个pprof工具,这个工具可以做CPU和内存的profiling。使用可以参考之前介绍文章:一个内部API系统的性能优化 - 知乎专栏
package main
import
(
"log"
"net/http"
_"net/http/pprof"
)
func main() {
go func() {
//port is you coustom define.
log.Println(http.ListenAndServe("localhost:7000", nil))
}()
//do your stuff.
}
只需要引入net/http 和 _"net/http/pprof"即可,然后配合工具生成流程图,占比图清晰明了。
或者对于一些程序你还可以在运行时去改变它,调试它,使用google/gops 谷歌出品,你可以去查看栈,内存,CPU,heap等等信息,很不错,但是我不喜欢它开启了服务端口,这个项目刚开始是不需要使用新的端口,直接使用套接字文件通信,但是因为无法在windows上实现,最后作罢,从此好感降低了!
当然你还可以使用GDB工具,最新的GDB貌似还加入了查看goruntine的命令,很棒!
6.算法与优化思路
这个不用多说,说实话个人觉得算法是区分工程师和码农的一个很大分界点,算法可以说是基本能力,很多人不以为然觉得只是面试门槛,但是看看代码实现中数据结构的设计和算法实现就明白了!当遇到问题会不自觉的想到一个算法,这个目的就够了,其实并没有说算法非常牛逼,其实之前老司机聊天也说过只要你能在遇到问题能够想到用什么算法解决即可。
各种排序,集合操作,查询等等,没有最好的算法,只要最适合的算法。
至于哪些方面需要优化,一方面是算法的效率还要就是现象,例如CPU特别高那么看看goruntine的调度,哪个函数占用比高,是不是存在死循环;内存大,看看是不是有大的内存分配没有及时回收,或者是不是有频繁的内存分配,是不是有内存泄露?响应慢是卡在哪里,是执行效率还是和组件通信等等。
7.Go的陷阱与技巧
a.make的陷阱
func main() {
s := make([]int, 3)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
结果
[0 0 0 1 2 3]
b.map读写冲突,产生竞态
c.文件打开,数据库连接记得一定要关闭或释放,一般使用defer
d.对于一个struct值的map,你无法更新单个的struct值
e.简化range
for range m {
}
f.defer的陷阱
有名返回值则是在函数声明的同时就已经被声明,匿名返回值是在return执行时被声明,所以在defer语句中只能访问有名返回值,而不能直接访问匿名返回值。
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", defer_call())
}
func defer_call() int {
var i int
defer func() {
i++
fmt.Println("defer1:", i)
}()
defer func() {
i++
fmt.Println("defer2:", i)
}()
return i
}
defer2: 1
defer1: 2
return: 0
Q2.
package main
import (
"fmt"
)
func main() {
fmt.Println("return:", defer_call())
}
func defer_call() (i int) {
defer func() {
i++
fmt.Println("defer1:", i)
}()
defer func() {
i++
fmt.Println("defer2:", i)
}()
return i
}
defer2: 1
defer1: 2
return: 2
g.短式变量声明的陷阱
那些使用过动态语言的开发者而言对于短式变量声明的语法很熟悉,所以很容易让人把它当成一个正常的分配操作。这个错误,将不会出现编译错误,但将不会达到你预期的效果。
package main
import "fmt"
func main() {
value := 1
fmt.Println(value) // prints 1
{
fmt.Println(value) // prints 1
value := 2
fmt.Println(value) // prints 2
}
fmt.Println(value) // prints 1 (bad if you need 2)
}
这个说到底是代码边界和变量影响范围问题。
h.nil和显式类型
nil标志符用于表示interface、函数、maps、slices和channels的“零值”。如果你不指定变量的类型,编译器将无法编译你的代码,因为它不知道具体的类型,同时你也不能给string赋nil值。
package main
func main() {
var value1 = nil // error
_ = value1
var value2 string = nil // error
if value2 == nil { // error
value2 = "test"
}
}
应该
package main
func main() {
var value1 interface{} = nil // error
_ = value1
var value2 string
if value2 == "" {
value2 = "test"
}
}
i.全部是值传递,没有引用传递
如果你是一个C或则C++开发者,那么知道数组就是指针。当你向函数中传递数组时,函数会参照相同的内存区域,这样它们就可以修改原始的数据。但Go中的数组是数值,因此当你向函数中传递数组时,函数会得到原始数组数据的一份复制。如果你打算更新数组的数据,你将会失败。
j.select下的所有case遍历是随机的,在使用的过程中要注意,这和switch是不同的
l.使用接口实现一个类型分类函数:
func classifier(items ...interface{}) {
for i, x := range items {
switch x.(type) {
case bool:
fmt.Printf("param #%d is a bool\n", i)
case float64:
fmt.Printf("param #%d is a float64\n", i)
case int, int64:
fmt.Printf("param #%d is an int\n", i)
case nil:
fmt.Printf("param #%d is nil\n", i)
case string:
fmt.Printf("param #%d is a string\n", i)
default:
fmt.Printf("param #%d’s type is unknown\n", i)
}
}
}
l.Map值在获取的时候是无序的,所以当我们需要有序时就需要通过字符串数组排序间接得到
package main
import (
"fmt"
"sort"
)
func main() {
var m = map[string]int{
"unix": 0,
"python": 1,
"go": 2,
"javascript": 3,
"testing": 4,
"philosophy": 5,
"startups": 6,
"productivity": 7,
"hn": 8,
"reddit": 9,
"C++": 10,
}
var keys []string
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println("Key:", k, "Value:", m[k])
}
}
m.init函数
开发过程我们经常会遇到主要逻辑开始前要声明或者一些全局的变量或者初始化操作,或者有时候我们仅仅需要import一些包,并不需要使用里面的函数,那就需要使用init初始化函数,一个package中可以有多个init,比如你在demo/A.go,demo/B.go都有一个init那么它们都会执行。
n.Go程序显示占用内存有时候并不是真正在用的内存,只是还没还给操作系统
o.雨痕老师的研究
p.Golang的五十度灰
中文版:Go的50度灰:Golang新开发者要注意的陷阱和常见错误
英文版:50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs
如何做Go的性能优化?(转)的更多相关文章
- Quick BI的复杂系统为例:那些年,我们一起做过的性能优化
背景 一直以来,性能都是技术层面不可避开的话题,尤其在中大型复杂项目中.犹如汽车整车性能,追求极速的同时,还要保障舒适性和实用性,而在汽车制造的每个环节.零件整合情况.发动机调校等等,都会最终影响用户 ...
- SQL性能优化案例分析
这段时间做一个SQL性能优化的案例分析, 整理了一下过往的案例,发现一个比较有意思的,拿出来给大家分享. 这个项目是我在项目开展2期的时候才加入的, 之前一期是个金融内部信息门户, 里面有个功能是收集 ...
- SQL性能优化
引言: 以前在面试的过程中,总有面试官问道:你做过sql性能优化吗?对此,我的答复是没有.一次没有不是自己的错误,两次也不是,但如果是多次呢?今天痛下决心,把有关sql性能优化的相关知识总结一下,以便 ...
- iOS回顾笔记(07) -- UITableView的使用和性能优化
iOS回顾笔记(07) -- UITableView的使用和性能优化 如果问iOS中最重要的最常用的UI控件是什么,我觉得UITableView当之无愧!似乎所有常规APP都使用到了UITableVi ...
- 常见性能优化策略的总结 good
阅读目录 代码 数据库 缓存 异步 NoSQL JVM调优 多线程与分布式 度量系统(监控.报警.服务依赖管理) 案例一:商家与控制区关系的刷新job 案例二:POI缓存设计与实现 案例三:业务运营后 ...
- SQL Server 数据库性能优化
分析比较执行时间计划读取情况 1. 查看执行时间和cpu set statistics time on select * from Bus_DevHistoryData set statistics ...
- Tomcat安全配置与性能优化
Tomcat 是 Apache软件基金会下的一个免费.开源的WEB应用服务器,它可以运行在 Linux 和 Windows 等多个平台上,由于其性能稳定.扩展性好.免费等特点深受广大用户喜爱.目前,很 ...
- Android 性能优化的方面方面都在这儿
又到周六了,鸿洋的不定期的周六放送又来了~~这次来谈谈性能优化吧.大家在工作中或多或少都会拿自家的应用和竞品app做比对,不可避免的需要做一些app性能优化的活.很多时候可能是策略上的调整,不过还是有 ...
- 数据库性能优化之SQL优化
网上有关SQL优化的方案有很多,但多是杂乱无章.近日闲暇抽空整理了一下,方便大家以后的查阅,若发现其中有什么问题和不全,欢迎大家在下面纠正和补充: 1. 对于SQL语句的性能优化,主要体现在对于查询语 ...
随机推荐
- ROS设备的性价比图
- Jmeter(十四)Logic Controller 之 If Controller
If Controller---如果控制器:属于逻辑判断类型的组件,其实学过代码的都知道if--else,while等都是常用的逻辑判断关键词,Jmeter也提供了逻辑判断--If Controlle ...
- echarts饼图配置
js引用和配置div <div id="container" style="height: 100%"></div> <scrip ...
- C# 连接数据库实现增删改查
class Program { private static string urls = "server=127.0.0.1;port=3306;user=root;password=123 ...
- xgboost的sklearn接口和原生接口参数详细说明及调参指点
from xgboost import XGBClassifier XGBClassifier(max_depth=3,learning_rate=0.1,n_estimators=100,silen ...
- CS RANK: AI & ML
http://csrankings.org/#/index?ai&mlmining 权威学术排名:30-100
- 使用Nginx+Lua实现自定义WAF
使用Nginx+Lua实现自定义WAF 版权声明:全部抄自赵班长的GitHub上waf项目 功能列表: 支持IP白名单和黑名单功能,直接将黑名单的IP访问拒绝. 支持URL白名单,将不需要过滤的URL ...
- RecyclerView添加条目点击事件setOnItemClickListener,不是在Adapter中设置;
RecyclerView不像ListView,可以直接写setOnItemClickListener,我们大部分都是在Adapter中的设置点击事件,这个是使用RecyclerView的addOnIt ...
- 在同一个Linux上配置多个git账户
1.首先在~/.ssh目录下执行 ssh-keygen -t rsa -C "miaoying.new@qq.com" 其中 -C "miaoying.new@qq.co ...
- django之前-----web应用与框架
一web应用 web应用程序是一种可以通过Web访问的应用程序,程序的最大好处是用户很容易访问应用程序,用户只需要有浏览器即可,不需要再安装其他软件.应用程序有两种模式C/S.B/S. 下面来看一个简 ...