转自:https://mp.weixin.qq.com/s/9IKaXeWTiiQTFlvZzxgsEA

记一次获得 3 倍性能的 go 程序优化实践,及 on-cpu / off-cpu 火焰图的使用

原创 2017-07-27 petergz 唯技术

先把结论列在前面:

1.Golang的性能可以做到非常好,但是一些native包的性能很可能会拖后腿,比如regexp和encoding/json。如果在性能要求较高的场合使用,要根据实际情况做相应优化。

2.on-cpu/off-cpu火焰图的使用是程序性能分析的利器,往往一针见血。虽然生成一张火焰图比较繁琐(尤其是off-cpu图),但绝对值得拥有!

之前一直使用Logstash作为日志文件采集客户端程序。Logstash功能强大,有丰富的数据处理插件及很好的扩展能力,但由于使用JRuby实现,性能堪忧。而Filebeat是后来出现的一个用go语言实现的,更轻量级的日志文件采集客户端。性能不错、资源占用少,但几乎没有任何解析处理能力。通常的使用场景是使用Filebeat采集到Logstash解析处理,然后再上传到Kafka或Elasticsearch。值得注意的是,Logstash和Filebeat都是Elastic公司的优秀开源产品。

为了提高客户端的日志采集性能,又减少数据传输环节和部署复杂度,并更充分的将go语言的性能优势利用于日志解析,于是决定在Filebeat上通过开发插件的方式,实现针对公司日志格式规范的解析,直接作为Logstash的替代品。

背景介绍完毕,下面是实现和优化的过程。

Version 1

先做一个最简单的实现,即用go自带的正则表达式包regexp做日志解析。性能已经比Logstash(也是通过开发插件做规范日志解析)高出30%。

这里的性能测试着眼于日志采集的瓶颈——解析处理环节,指标是在限制只使用一个cpu core的条件下(在服务器上要尽量减少对业务应用的资源占用),采集并解析1百万条指定格式和长度的日志所花费的时间。测试环境是1台主频为3.2GHz的PC。为了避免disk IO及page cache的影响,将输入文件和输出文件都放在/dev/shm中。对于Filebeat的CPU限制,是通过启动时指定环境变量GOMAXPROCS=1实现。

这一版本处理1百万条日志花费的时间为122秒,即每秒8200条日志。

Version 2

接下来尝试做一些优化,看看这个go插件的性能还可不可以有些提升。首先想到的是替换regexp包。Linux9下有一个C实现的PCRE库,github.com/glenn-brown/golang-pkg-pcre/src/pkg/pcre这个第三方包正是将PCRE库应用到golang中。CentOS下需要先安装pcre-devel这个包。

这个版本的处理时间为97秒,结果显示比第一个版本的处理性能提升了25%。

Version 3

第三个版本,是完全不使用正则表达式,而是针对固定的日志格式规则,利用strings.Index()做字符串分解和提取操作。这个版本的处理时间为70秒,性能又大大的提升了将近40%。

Version 4

那还有没有进一步提升的空间呢。有,就是Filebeat用作序列化输出的json包。我们的日志上传使用json格式,而Filebeat使用go自带的encoding/json包是基于反射实现的,性能一直广受诟病。如果对json解析有优化的话,性能提高会是很可观的。既然我们的日志格式是固定的,解析出来的字段也是固定的,这时就可以基于固定的日志结构体做json的序列化,而不必用低效率的反射来实现。go有多个针对给定结构体做json序列化/反序列化的第三方包,我们这里使用的是easyjson(https://github.com/mailru/easyjson)。在安装完easyjson包后,对我们包含了日志格式结构体定义的程序文件执行easyjson命令,会生成一个xxx_easyjson.go的文件,里面包含了这个结构体专用的Marshal/Unmarshal方法。这样一来,处理时间又缩短为61秒,性能提高15%。

这时,代码在我面前,已经想不出有什么大的方面还可以优化的了。是时候该本文的另一个主角,火焰图出场了。

火焰图是性能分析的一个有效工具,这里(http://www.brendangregg.com/flamegraphs.html)是它的说明。通常看到的火焰图,是指on-cpu火焰图,用来分析cpu都消耗在哪些函数调用上。

安装完FlameGraph(https://github.com/brendangregg/FlameGraph)工具后,先对目前版本的程序运行一次性能测试,按照说明抓取数据生成火焰图如下。

FlameGraph对于c/go程序是通用的。对于go程序,也可以使用自带的net/http/pprof包作为数据源,然后安装uber的go-torch(https://github.com/uber/go-torch)工具来自动调用FlameGraph脚本生成on-cpu火焰图,执行会稍为简便一些。参见go-torch说明。

图中纵向代表的是函数调用栈,横向各个方块的宽度代表的是占用cpu时间的比例,需要留意的是靠近顶端的大长条。方块的颜色是随机的没有实际意义。

从上图可以看到cpu时间占用最多的主要有两块。一块是Output处理部分,稍为大头的是json处理,这块已经优化过没什么可以做的了。另一块就比较奇怪了,是common.MapStr.Clone()方法,居然占了40%的cpu时间。再往上看,主要是Errorf的处理。一看代码,马上明白了。

common.MapStr是在pipeline中存放日志内容的结构体,它的Clone()方法实现里判断一个子键值是否为嵌套的Mapstr结构时,是通过判断toMapStr()方法是否返回error。从这里看,生成error对象的代价是非常可观的。于是,一个显然的fix,就是将toMapStr()中的判断方法移到Clone()中并避免生成error。

Version 5

对修改后的代码重新生成一张火焰图如下。

这时common.MapStr.Clone()从图中已经几乎找不见了,证明花费的cpu时间已经可以忽略不计。

测试时间一下子缩短到了46秒,节省了33%,非常大的改善!

到现在,还有一个之前未提到的问题没有解决——在限制使用一个core之后,测试运行时cpu利用率只能跑到82%左右。是不是由于有锁存在影响了性能呢?

这时候,又该请off-cpu火焰图出场了。Off-cpu火焰图,是用来分析程序没有有效利用cpu的时候,消耗在什么地方了,在这里(http://www.brendangregg.com/FlameGraphs/offcpuflamegraphs.html)有详细的介绍。数据收集比on-cpu火焰图要复杂,可以使用大名鼎鼎的春哥提供的openresty-systemtap-toolkit(https://github.com/openresty/openresty-systemtap-toolkit)包。春哥的项目页面中没有详细说明的是kernel-devel和debuginfo包的安装方法。在此也记录一下。

# kernel-devel没有问题,直接yum安装

sudo yum install -y kernel-devel

# debuginfo,在CentOS7中需要这样装

sudo vim /etc/yum.repos.d/CentOS-Debuginfo.repo

修改为enable=1

sudo debuginfo-install kernel

安装时可能还会报错:

Invalid GPG Key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-Debug-7: No key found in given key data

需要从https://www.centos.org/keys/RPM-GPG-KEY-CentOS-Debug-7下载key写入到/etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-Debug-7

安装完后按照说明生成了off-cpu火焰图如下:

我还不能完全解读这张图,但是已经可以明显看到,对Registry文件(Filebeat用于记录文件采集列表和offset数据)的写操作占了一定比例。于是,尝试将Filebeat的spool_size(每完成这么多条日志更新一次Registry文件)设置为10240,默认值的5倍,运行测试cpu已经可以跑到95%以上。而将Registry设置到/dev/shm/下也同样可以解决测试时cpu跑不满的问题。

这就否定了上面对锁使用不当影响性能的猜测。在实际应用时spool_size的设置应当依据结合了output端(如写入到Kafka)的测试数据来决定。

至此,优化结束,达到了最初版本性能的3倍!
各个版本的具体运行性能数据如下图所示。

需要稍作说明的是:

1.Filebeat开发是基于5.3.1版本,go版本是1.8

2.Logstash的测试通过-w 1参数配置使用一个

工作进程,并未限制使用一个core

3.执行时间包括了程序的启动时间(Logstash的启动时间有将近20秒)

最终的优化结果是,针对特定格式和长度的日志解析能力在PC上达到了每秒25000条,即使在CPU主频较低的生产服务器上,也可以达到每秒20000条。

Go的高性能真不是吹的,当然是要在足够的优化后:)

记一次获得 3 倍性能的 go 程序优化实践,及 on-cpu / off-cpu 火焰图的使用的更多相关文章

  1. 火焰图--记一次cpu降温过程

    引子 正值周末,娃儿6:30又如闹铃般准时来叫醒了我们.年前离开美菜,又回到了杭州.原本是想有更多时间陪伴娃儿,然而新的工作节奏与工作地点,让我们每天都是早上见面:这不,为了周末可以多玩一会儿,早早就 ...

  2. 记一次 java程序优化

    优化原因 环境中部署两个程序: web应用 tomcat   10G(webservice服务端,前端web服务) java应用               5G(webservice客户端,sock ...

  3. 记一次按需加载和npm模块发布实践

    按需加载 在使用 lodash 的时候我们可以使用这样的代码 //一 import {omit} from "lodash"; //二 import l from "lo ...

  4. 记一次开发:Qt简单电话本程序

    前言 断断续续学习C++一年了,现在要做课设,觉得控制台界面实在太难看,于是用Qt做一个图形化的程序出来. 学习Qt也没有多久,只是了解了个大概,这次开发基本上是啃了2天的官方帮助文档,然后利用各种Q ...

  5. 记一次在BroadcastReceiver或Service里弹窗的“完美”实践

    事情是这样的,目前在做一个医疗项目,需要定时在某个时间段比如午休时间和晚上让我们的App休眠,那么这个时候在休眠时间段如果用户按了电源键点亮屏幕了,我们就需要弹出一个全屏的窗口去做一个人性化的提示,“ ...

  6. 记一次 .NET 某新能源汽车锂电池检测程序 UI挂死分析

    更多高质量干货:参见我的 GitHub: dotnetfly 一:背景 1. 讲故事 这世间事说来也奇怪,近两个月有三位朋友找到我,让我帮忙分析下他的程序hangon现象,这三个dump分别涉及: 医 ...

  7. 记一次用mpvue框架搭建的小程序

    介绍 mpvue (github 地址请参见)是一个使用 Vue.js 开发小程序的前端框架.框架基于 Vue.js 核心,mpvue 修改了Vue.js 的 runtime 和 compiler 实 ...

  8. APK瘦身记,如何实现高达53%的压缩效果

    作者:非戈@阿里移动安全 1.我是怎么思考这件事情的 APK是Android系统安装包的文件格式,关于这个话题其实是一个老生常谈的题目,不论是公司内部,还是外部网络,前人前辈已经总结出很多方法和规律. ...

  9. [转]10+倍性能提升全过程--优酷账号绑定淘宝账号的TPS从500到5400的优化历程

    摘要: # 10+倍性能提升全过程--优酷账号绑定淘宝账号的TPS从500到5400的优化历程 ## 背景说明 > 2016年的双11在淘宝上买买买的时候,天猫和优酷土豆一起做了联合促销,在天猫 ...

随机推荐

  1. python 类中的方法

    首先,方法是类内部定义的函数,所以方法是类的属性而不是实例的属性. 其次,方法只能在所属的类拥有实例的时候才能被调用.当存在一个实例的时候,我们可以说方法被绑定到实例.如果没有实例,那么我们就说方法是 ...

  2. HDU 4522

    DIJK,最短路,建两个图就好了. #include <cstdlib> #include <cstdio> #include <cstring> #include ...

  3. ubuntu网卡ip的配置

    ifconfig 命令的结果 和 ip addr (或者查看具体网卡的是 ip addr show eth0) 看到的结果不一样, ip addr show eth0 可以看到eth0网卡上面的多个 ...

  4. Java 两个整数相除保留两位小数,将小数转化为百分数

    Java 两个整数相除保留两位小数,将小数转化为百分数 源于:http://blog.sina.com.cn/s/blog_624d755d0101cvuq.html 后来学习了:http://blo ...

  5. VB.NET机房收费 &amp; 抽象工厂模式

    学习设计模式的时候,提到了一个专门訪问数据库的模式-抽象工厂模式,记得当时举样例理解的时候并未设计到数据库,仅仅是大概了了解了一下,如今对于机房收费系统涉及到了数据库的管理,借此机会好好学习一下.用常 ...

  6. 在CentOS VPS上源代码安装高版本号git

    背景:个别软件在国内下载非常慢,在vps下载就非常快. 可是下载好后的文件通过scp弄出来的时候又非常慢,所以想通过在vps里安装git,通过gitlab或oschina来进行中转.但遗憾的是,上传到 ...

  7. OC基础回想(十二)协议

    在OC基础(十一)中我们讨论了类别和非正式协议的奇异之处.在使用非正式协议时.能够仅仅实现你想要获得响应的方法.也不必在对象中声明不论什么内容来表示该对象可用作托付对象. 全部这些任务能够用最少的代码 ...

  8. Codeforces Round #330 (Div. 2) D. Max and Bike 二分

    D. Max and Bike For months Maxim has been coming to work on his favorite bicycle. And quite recently ...

  9. Android中关闭DatePicker、TimePicker、NumberPicker的可编辑模式

    DatePicker.TimePicker.NumberPicker这三个控件在使用的过程中,用户点击数字会弹出键盘,有时候会造成布局被挤压不好看,也有其他的需求. 我看了网上很多文章的解决办法都无效 ...

  10. [NOI 2003] 逃学的小孩

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1509 [算法] 树的直径 [代码] #include<bits/stdc++. ...