初识go语言-站在java视角看待go的性能优化 | 青训营笔记

199 阅读6分钟

这是我参与「第五届青训营 」笔记创作活动的第5天

笔者才学疏浅,如有问题请指出,先在此祝大家新年快乐。

前言

早在以前的那种java性能不好的时代已经过去了,或许在语言本身的性能上java并不太占优势,但是在经过层层优化过后依旧能在业务场景中取得比较优秀的成绩。本篇文章,笔者将从java的视角来学习和看待go的性能优化。

为什么要性能优化

  • 无法满足基本业务功能:当一个系统的流量突然增大,系统本身的性能不足而导致一些业务场景不可用,这时候就需要进行一些性能优化了。
  • 无法满足用户的体验感:程序是用来给用户使用的,如果因为各种程序性能问题(延迟、系统错误、响应缓慢等)造成用户的体验感极差,会导致用户流失。俗话说得好,只有功能没有性能的程序不是一个好程序(相信我们也不想玩一个放技能卡半天的游戏吧)。
  • 高效利用资源,节省成本:我们都知道大型的系统通常部署在集群服务器上的,少则几十台多则数百台,而服务器成本高资源有限,对程序系统进行优化即可节省相当一部分资源,用有限的资源干更多的事。 性能优化的理由有很多,上面只是最主要最常见的三种情况。

常见的几种优化方式

设计优化

设计优化是性能优化里面最通用的一种,不关乎开发语言,更注重系统开发前对整体的一个考虑和把控。架构师一般会考虑到系统的各种潜在问题和技术难点,综合各种因素后给出一个合理的设计方案。设计优化要用到很多分析方法、设计模式、优化思想,通常对系统的优化是质的提升。一个设计优良的系统既便于维护又有高效性能。

数据库优化

做一个完整的系统,那必然需要用到数据库,所以数据库的优化也是无关系统开发语言且非常重要的。在数据库的整体上可以依情况考虑分库分表、数据库集群或者读写分离。对于同一个库里面可能要注重数据库表结构和关系的设计,这是数据库性能进一步优化的基础。在系统里面的数据应用层可以对SQL进行优化,不同的SQL得到同样的结果,但SQL的运行效率不一样。

代码优化

使用不同的开发语言,代码优化的方式不一样,但遵循的原则和思想都差不多,下面可以看一下java的代码优化和go的代码中优化。

java常见的代码优化

  • 遍历数组使用stream的效率高于增强for循环高于原始for循环。
  • 尽可能地去避免创建更多的对象,每创建一个对象都要开辟一片内存空间,使用完对象还要进行垃圾回收处理,很占用内存空间。
  • 字符串的处理,使用builder而不用buffer,java的StringBuffer存在线程安全问题。
  • 如果有IO流操作,使用完后一定要关闭通道,不然内存开销很大,短时间或许看不出什么问题,但时间一长可能会导致大问题。

这只是部分java的代码优化方式,详情可以参考必会的 55 个 Java 性能优化细节!一网打尽! - 腾讯云开发者社区-腾讯云 (tencent.com)

go常见的代码优化

slice预分配内存
  • 在尽可能的情况下,使用 make() 初始化切片时提供容量信息,特别是在追加切片时。
  • 切片本质是一个数组片段的描述,包括了数组的指针,这个片段的长度和容量(不改变内存分配情况下的最大长度)。
  • 切片操作并不复制切片指向的元素,创建一个新的切片会复用原来切片的底层数组,因此切片操作是非常高效的。
  • 切片有三个属性,指针(ptr)、长度(len) 和容量(cap)。append 时有两种场景:当 append 之后的长度小于等于 cap,将会直接利用原底层数组剩余的空间;当 append 后的长度大于 cap 时,则会分配一块更大的区域来容纳新的底层数组。
map预分配内存
  • 不断向map里面添加元素会触发map的扩容。(笔者以前用c++的vector,迭代器失效了,导致vector无限扩容一秒就往文件写入了数个G的垃圾字符)
  • 根据实际需要预估空间。
  • 提前分配好空间可以减少内存拷贝和rehash消耗。
string处理
  • 这里和java一样常见的字符串拼接方式是builder、buffer和+,常用的字符串处理方式是前两个。
  • builder效率高于buffer。

这里可以对比一下java的字符串和go的字符串。

java字符串:java创建字符串使用new String()构造器方法时,每次创建一个字符串(即使初值一样)都会开辟一个新的空间;直接使用String创建字符串时,java底层会将该字符串放入一块字符串常量池,当再次用String创建字符串时,底层在常量池里寻找,如果有一样的(字符串值一样)就用原来的地址和内存空间,如果没有就开辟新的地址和内存空间。大概就这样,不算详细,感兴趣的朋友可以自己打印字符串地址试一下。

go字符串:字符串在go语言中是不可变类型,占用内存大小是固定的,当使用+拼接 2 个字符串时,生成一个新的字符串,那么就需要开辟一段新的空间,新空间的大小是原来两个字符串的大小之和。builder和buffer底层都是使用的byte数组,buffer在转化的时候重新申请了一块空间来存放字符串变量,builder直接将byte数组转化成了字符串类型返回。

小结

一个好的程序不能只有功能而没有性能,对于一个程序来说性能优化显得异常重要。从java的角度来看go的性能优化,通过对比我们可以发现不同语言性能优化的异同之处。从语言性能上java不如go,所以有更多的优化方式,从程序系统的整体考量上,设计优化和数据库优化通用且重要。

参考