性能优化,简而言之,就是在不影响系统运行正确性的前提下,使之运行地更快,完成特定功能所需的时间更短。
正确高效使用数据结构
正确使用Slice
|
|
在通过函数传递slice类型的参数时,需要注意由于Go语言默认按值传递参数,调用函数传入的参数与函数内部得到的参数实际上是不相同的。但是slice类型中包含一个指向真正数据存储位置的指针,因而在调用参数类型为slice的函数时,对其进行修改也会导致原slice的数据变更。 但是,如果在调用函数中,对该slice进行了append操作导致slice扩容进而导致slice的array指向其他位置时,对函数内的slice进行任意修改都不会影响到函数外的slice。
警告
在函数参数有slice类型时,应尽量避免对slice进行append操作。
高效使用slice
预先分配内存可以提升性能。
直接使用index赋值而非append操作可以提升性能。
Bounds Checking Elimination
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
func normal(s []int) { i := 0 i += s[0] i += s[1] i += s[2] i += s[3] println(i) } // Better Performance! func bce(s []int) { _ = s[3] i := 0 i += s[0] i += s[1] i += s[2] i += s[3] println(i) }
在normal函数中,每次从slice取数据时,都会进行一次Bounds Check,导致性能下降。在bce函数中,我们一开始取s[3],进行了一次bounds check,此时编译器便可确定下标小于等于3的位置不需要进行bounds check,可以拥有较好的性能。
正确使用map
- map并发读写问题。 Go语言中的map是线程不安全的,因而如果有多个协程同时对一个map进行读写的话,会报出fatal级的错误。
- map的内存占用。 map在delete操作时不会释放底层的存储。
正确使用string
|
|
在Go语言的定义中,string是不可变的,[]byte是可变的,当把string转换为[]byte后,不能进行变更。
高效使用string
拼接string时,尽量使用strings.Builder,在使用strings.Builder时,我们可以使用(b *Builder) Grow(n int)函数对Builder进行容量的预分配。
正确使用channel
在Go语言中,select语句用于监听和channel有关的IO操作,当IO操作发生时,触发对应case的操作。
|
|
在使用select语句时,需要注意:
- 如果不设置default条件,当没有IO操作发生则会一直阻塞。
- 当有多个IO操作时,会随机选择一个case执行,无法保证执行的顺序。
- 对于在for中的select操作,不能添加default,否则会出现CPU占用过高的问题。
- 在for语句中的select操作,执行break操作只会跳出select。
高效使用channel
- 并发编程中,正确性最重要。
- channel仅应该传递通知,而不是传递值。
- 注意buffered和unbuffered channel的区别(尽量用buffered)