首先普及一下
队列:先进先出
栈:先进后出,在程序调用的时候从栈空间去分配(栈分配廉价)
堆:在程序调用的时候从系统的内存 区 分配(堆分配昂贵)
golang中的值类型和引用类型
值类型:基本数据类型int, float,bool, string以及数组和struct
* 注释:值类型:变量直接存储值,内容通常在栈中分配
引用类型:指针,slice,map,chan,interface,func等都是引用类型
* 注释:引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
常用关键字
字符串string类型:
关于字符串类型,用的最多的是拼接。字符串拼接也是有优化技巧的。
很多人在网上看到一些所谓的“老师”,说使用strings.builder性能是最优的,那今天我就要打脸啦!
我直接把结果告诉大家。
通过不断的调试,对结果进行分析,得出如下结论
1、第一个临界点是5次,意思是指,做字符串拼接的时候,直接用“+”号来做拼接,如果操作5次以内的次数(不包含5次),性能优于 strings.Builder和bytes.Buffer,达到5次后strings.Builder性能是最佳的,比其他的两个都好
2、第二个临界点是15次,意思是指,做字符串拼接的时候,直接用“+”号来做拼接,如果操作15次以内的次数(不包含15次),性能优于 strings.Builder和bytes.Buffer,达到15次后bytes.Builder性能是最佳的,比其他的两个都好。
这是在网上摘抄的一个demo,证明strings.builder的性能好。请问你会闲的无聊有事没事拼接10000次的字符串,如果这样做要么是你业务逻辑有问题,要么是你的方式有问题。
结论是依靠实践来证明的,我这里有代码和单元测试。文章结尾,还是会把源码分享出来。
for && range
range:适合用在map,chan,slice
区别:在range开始迭代时就浅拷贝了一个副本,对数组来说,相当于拷贝了一个新的数组进行迭代,修改原数组不会影响被迭代数组。而对于切片来说,range拷贝出来的切片与原切片底层是同一个数组,因此对原切片的修改也会影响到被迭代切片
数组和切片
- 数组
1、申明使用 - 切片
- 数组与切片之间的区别
1、切片是引用类型,数组是值类型
2、切片比数组多一个属性—容量(cap)
3、数组的长度是固定的,切片的长度是不固定(切片是动态的数组)
4、切片的底层是数组
defer
是先进先出还是后进先出 ?
答案是先进后出
在1.14版本以后,defer几乎是0开销
panci 和 recover
作用:在golang当中不存在tye ... catch 异常处理逻辑。在golang当中使用defer, panic和recover来控制程序执行流程,借此来达到处理异常的目的。
Panic是一个可以停止程序执行流程的内置函数。
new和make
- 总结:new 的作用是初始化一个指向类型的指针(*T),make 的作用是为 slice,map 或 chan 初始化并返回引用(T)。
- new 的用法
- make 的用法
make有以下三种不同的用法:
1. make(map[string]string)
2. make([]int, 2)
3. make([]int, 2, 4) - 解释
1. 第一种用法,即缺少长度的参数,只传类型,这种用法只能用在类型为map或chan的场景,例如 make([]int)是会报错的。这样返回的空间长度都是默认为0的。
2. 第二种用法,指定了长度,例如make([]int, 2)返回的是一个长度为2的slice
3. 第三种用法,第二参数指定的是切片的长度,第三个参数是用来指定预留的空间长度,例如a := make([]int, 2, 4), 这里值得注意的是返回的切片a的总长度是4,预留的意思并不是另外多出来4的长度,其实是包含了前面2个已经切片的个数的。所以举个例子当你这样用的时候 a := make([]int, 4, 2),就会报语法错误。
因此,当我们为slice分配内存的时候,应当尽量预估到slice可能的最大长度,通过给make传第三个参数的方式来给slice预留好内存空间,这样可以避免二次分配内存带来的开销,大大提高程序的性能。
而事实上,我们其实是很难预估切片的可能的最大长度的,这种情况下,当我们调用append为slice追加元素时,golang为了尽可能的减少二次分配内存,并不是每一次都只增加一个单位的内存空间,而且遵循这样一种扩容机制:
当有预留的未使用的空间时,直接对未使用的空间进行切片追加,当预留的空间全部使用完毕的时候,扩容的空间将会是当前的slice长度的一倍,例如当前slice的长度为4,进行一次append操作之后,cap(a)返回的长度将会是8 - new和make的区别
并不是协程数量越多越好,因为它要做上下文的切换,我做的数据实验表明,协程数量在5000以内(在我的工作电脑上做出的实验数据)
select
select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
语法如下:
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */default : /* 可选 */ statement(s);
}
channel
- channel 用法
1、有缓冲和无缓冲
无缓冲的与有缓冲channel有着重大差别,那就是一个是同步的 一个是非同步的
比如
c1:=make(chan int) 无缓冲
c2:=make(chan int,1) 有缓冲
c1<-1
无缓冲: 不仅仅是向 c1 通道放 1,而是一直要等有别的携程 <-c1 接手了这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着。
有缓冲: c2<-1 则不会阻塞,因为缓冲大小是1(其实是缓冲大小为0),只有当放第二个值的时候,第一个还没被人拿走,这时候才会阻塞。 - channel 在什么情况下会发生panic
1、重复close一个channel
2、向已经closed的channel继续发送消息
goruntime
- 并发
我的理解:两个人同时在干一件事,比如秒杀活动(如果理解有偏差请赐教) - 并行
我的理解:两件事同时发生(如果理解有偏差请赐教)
单元测试和基准测试
基准测试,是一种测试代码性能的方法,比如你有多种不同的方案,都可以解决问题,那么到底是那种方案性能更好呢?这时候基准测试就派上用场了。
基准测试主要是通过测试CPU和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。比如链接池的数量不是越多越好,那么哪个值才是最优值呢,这就需要配合基准测试不断调优了。
Go 性能调优之 —— 技巧
- 1、减少分配
- 2、优化字符串链接操作
- 3、已知长度时切片一次分配好
Append 操作虽然方便,但是有代价。切片的增长在元素到达 1024 个之前一直是两倍左右地变化,在到达 1024 个之后之后大约是 25% 地增长。在我们 append 之后的容量是多少呢?所以最好是一次分配好 - 4、要了解 goroutine 什么时候退出
- 5、Go 对一些请求使用高效的网络轮询
- 6、注意程序中的 IO 复杂度
- 7、使用流式 IO 接口
- 8、尽量使用稳定的新版本
Go 1.4 不应该再使用。
Go 1.5 和 1.6 编译器的速度更慢,但它产生更快的代码,并具有更快的 GC。
Go 1.7 的编译速度比 1.6 提高了大约 30%,链接速度提高了2倍(优于之前的Go版本)。
Go 1.8 在编译速度方面带来较小的改进,且在非Intel体系结构的代码质量方面有显著的改进。
Go 1.9,1.10,1.11 继续降低 GC 暂停时间并提高生成代码的质量。
Go 1.10 test 包引进了一个新的智能cache,运行会测试后会缓存测试结果。如果运行完一次后没有做 任何修改,那么开发者就不需要重复运行测试,节省时间。
Go 1.11 带来了一个重要的新功能:Go modules
Go 1.12 基于 analysis 包重写了 go vet 命令,为开发者写自己的检查器提供了更大的灵活性。
Go 1.13 改进了 sync 包中的 Pool,在 gc 运行时不会清除 pool。重写了逃逸分析,减少了 Go 程序中堆上的内存申请的空间。
Go 1.14 进一步提升 defer 性能、页分配器更高效,同时 timer 也更高效
源码地址: