slice是什么时候决定要扩张?

网上说slice的文章已经很多了,大都已经把slice的内存扩张原理都说清楚了。但是是如何判断slice是否需要扩张这个点却没有说的很清楚。想当然的我会觉得这个append是否扩张的逻辑应该隐藏在runtime中的某个函数,根据append的数组的长度进行判断。但是是否是如此呢?

本着这个疑问,我做了如下的实验。

我写了两个方法,一个需要扩张,一个不需要扩张。

无需扩张

不需要扩张的代码如下:

package main

func main() {
a := make([]int, 1, 3)
a = append(a, 4)
println(a)
}

使用 go tool objdump 来打印出编译后的main汇编码:

TEXT main.main(SB) /Users/yejianfeng/Documents/gopath/src/demo/append.go
append.go:3 0x104e140 65488b0c2530000000 MOVQ GS:0x30, CX
append.go:3 0x104e149 483b6110 CMPQ 0x10(CX), SP
append.go:3 0x104e14d 7661 JBE 0x104e1b0
append.go:3 0x104e14f 4883ec38 SUBQ $0x38, SP
append.go:3 0x104e153 48896c2430 MOVQ BP, 0x30(SP)
append.go:3 0x104e158 488d6c2430 LEAQ 0x30(SP), BP
append.go:4 0x104e15d 48c744241800000000 MOVQ $0x0, 0x18(SP)
append.go:4 0x104e166 0f57c0 XORPS X0, X0
append.go:4 0x104e169 0f11442420 MOVUPS X0, 0x20(SP)
append.go:5 0x104e16e 48c744242004000000 MOVQ $0x4, 0x20(SP)
append.go:6 0x104e177 e86445fdff CALL runtime.printlock(SB)
append.go:6 0x104e17c 488d442418 LEAQ 0x18(SP), AX
append.go:6 0x104e181 48890424 MOVQ AX, 0(SP)
append.go:6 0x104e185 48c744240802000000 MOVQ $0x2, 0x8(SP)
append.go:6 0x104e18e 48c744241003000000 MOVQ $0x3, 0x10(SP)
append.go:6 0x104e197 e8f44efdff CALL runtime.printslice(SB)
append.go:6 0x104e19c e8bf47fdff CALL runtime.printnl(SB)
append.go:6 0x104e1a1 e8ba45fdff CALL runtime.printunlock(SB)
append.go:7 0x104e1a6 488b6c2430 MOVQ 0x30(SP), BP
append.go:7 0x104e1ab 4883c438 ADDQ $0x38, SP
append.go:7 0x104e1af c3 RET
append.go:3 0x104e1b0 e82b89ffff CALL runtime.morestack_noctxt(SB)
append.go:3 0x104e1b5 eb89 JMP main.main(SB)

这个汇编码的逻辑在append.go第5行就只有一个MOV指令,将4直接放到指定的内存地址。

需要扩张

我的另一个需要扩张的代码如下:

package main

func main() {
a := make([]int, 1, 1)
a = append(a, 4)
println(a)
}

生成的汇编码如下:

TEXT main.main(SB) /Users/yejianfeng/Documents/gopath/src/demo/append.go
append.go:3 0x104e140 65488b0c2530000000 MOVQ GS:0x30, CX
append.go:3 0x104e149 483b6110 CMPQ 0x10(CX), SP
append.go:3 0x104e14d 0f86b0000000 JBE 0x104e203
append.go:3 0x104e153 4883ec68 SUBQ $0x68, SP
append.go:3 0x104e157 48896c2460 MOVQ BP, 0x60(SP)
append.go:3 0x104e15c 488d6c2460 LEAQ 0x60(SP), BP
append.go:5 0x104e161 48c744245000000000 MOVQ $0x0, 0x50(SP)
append.go:5 0x104e16a 488d05af9d0000 LEAQ type.*+40128(SB), AX
append.go:5 0x104e171 48890424 MOVQ AX, 0(SP)
append.go:5 0x104e175 488d442450 LEAQ 0x50(SP), AX
append.go:5 0x104e17a 4889442408 MOVQ AX, 0x8(SP)
append.go:5 0x104e17f 48c744241001000000 MOVQ $0x1, 0x10(SP)
append.go:5 0x104e188 48c744241801000000 MOVQ $0x1, 0x18(SP)
append.go:5 0x104e191 48c744242002000000 MOVQ $0x2, 0x20(SP)
append.go:5 0x104e19a e8b16bfeff CALL runtime.growslice(SB)
append.go:5 0x104e19f 488b442428 MOVQ 0x28(SP), AX
append.go:5 0x104e1a4 4889442458 MOVQ AX, 0x58(SP)
append.go:5 0x104e1a9 488b4c2430 MOVQ 0x30(SP), CX
append.go:5 0x104e1ae 48894c2448 MOVQ CX, 0x48(SP)
append.go:5 0x104e1b3 488b542438 MOVQ 0x38(SP), DX
append.go:5 0x104e1b8 4889542440 MOVQ DX, 0x40(SP)
append.go:5 0x104e1bd 48c7400804000000 MOVQ $0x4, 0x8(AX)
append.go:6 0x104e1c5 e81645fdff CALL runtime.printlock(SB)
append.go:6 0x104e1ca 488b442458 MOVQ 0x58(SP), AX
append.go:6 0x104e1cf 48890424 MOVQ AX, 0(SP)
append.go:5 0x104e1d3 488b442448 MOVQ 0x48(SP), AX
append.go:5 0x104e1d8 48ffc0 INCQ AX
append.go:6 0x104e1db 4889442408 MOVQ AX, 0x8(SP)
append.go:6 0x104e1e0 488b442440 MOVQ 0x40(SP), AX
append.go:6 0x104e1e5 4889442410 MOVQ AX, 0x10(SP)
append.go:6 0x104e1ea e8a14efdff CALL runtime.printslice(SB)
append.go:6 0x104e1ef e86c47fdff CALL runtime.printnl(SB)
append.go:6 0x104e1f4 e86745fdff CALL runtime.printunlock(SB)
append.go:7 0x104e1f9 488b6c2460 MOVQ 0x60(SP), BP
append.go:7 0x104e1fe 4883c468 ADDQ $0x68, SP

这里的第5行就和之前的那个大不一样了。有非常多的逻辑。基本进入第五行做的事情就是开始准备调用runtime.growslice的逻辑了

append.go:5		0x104e161		48c744245000000000	MOVQ $0x0, 0x50(SP)
append.go:5 0x104e16a 488d05af9d0000 LEAQ type.*+40128(SB), AX
append.go:5 0x104e171 48890424 MOVQ AX, 0(SP)
append.go:5 0x104e175 488d442450 LEAQ 0x50(SP), AX
append.go:5 0x104e17a 4889442408 MOVQ AX, 0x8(SP)
append.go:5 0x104e17f 48c744241001000000 MOVQ $0x1, 0x10(SP)
append.go:5 0x104e188 48c744241801000000 MOVQ $0x1, 0x18(SP)
append.go:5 0x104e191 48c744242002000000 MOVQ $0x2, 0x20(SP)
append.go:5 0x104e19a e8b16bfeff CALL runtime.growslice(SB)

这里就很明显了,所以slice的append是否进行cap扩张是在编译器进行判断的?至少我上面的两个代码,编译器编译的时候是知道这个slice是否需要进行扩张的,根据是否进行扩张就决定是否调用growslice。

再复杂的case

在雨痕群里问了下这个问题,有位群友给了个更为复杂点的case:

package main

func main() {
a := make([]int, 1, 5)
b := 3
for i := 0; i < b; i++ {
a = append(a, 4)
}
println(a)
}

这里的append是包围在for循环里面的,编译器其实就很难判断了。我们看下汇编:

TEXT main.main(SB) /Users/yejianfeng/Documents/gopath/src/demo/append.go
append.go:3 0x104e140 65488b0c2530000000 MOVQ GS:0x30, CX
append.go:3 0x104e149 488d4424f0 LEAQ -0x10(SP), AX
append.go:3 0x104e14e 483b4110 CMPQ 0x10(CX), AX
append.go:3 0x104e152 0f86fb000000 JBE 0x104e253
append.go:3 0x104e158 4881ec90000000 SUBQ $0x90, SP
append.go:3 0x104e15f 4889ac2488000000 MOVQ BP, 0x88(SP)
append.go:3 0x104e167 488dac2488000000 LEAQ 0x88(SP), BP
append.go:4 0x104e16f 48c744245800000000 MOVQ $0x0, 0x58(SP)
append.go:4 0x104e178 0f57c0 XORPS X0, X0
append.go:4 0x104e17b 0f11442460 MOVUPS X0, 0x60(SP)
append.go:4 0x104e180 0f11442470 MOVUPS X0, 0x70(SP)
append.go:4 0x104e185 31c0 XORL AX, AX
append.go:4 0x104e187 488d4c2458 LEAQ 0x58(SP), CX
append.go:4 0x104e18c ba01000000 MOVL $0x1, DX
append.go:4 0x104e191 bb05000000 MOVL $0x5, BX
append.go:6 0x104e196 eb0e JMP 0x104e1a6
append.go:7 0x104e198 48c704d104000000 MOVQ $0x4, 0(CX)(DX*8)
append.go:6 0x104e1a0 48ffc0 INCQ AX
append.go:9 0x104e1a3 4889f2 MOVQ SI, DX
append.go:9 0x104e1a6 4889542448 MOVQ DX, 0x48(SP)
append.go:6 0x104e1ab 4883f803 CMPQ $0x3, AX
append.go:6 0x104e1af 7d51 JGE 0x104e202
append.go:7 0x104e1b1 488d7201 LEAQ 0x1(DX), SI
append.go:7 0x104e1b5 4839de CMPQ BX, SI
append.go:7 0x104e1b8 7ede JLE 0x104e198
append.go:6 0x104e1ba 4889442440 MOVQ AX, 0x40(SP)
append.go:7 0x104e1bf 488d05ba9d0000 LEAQ type.*+40128(SB), AX
append.go:7 0x104e1c6 48890424 MOVQ AX, 0(SP)
append.go:7 0x104e1ca 48894c2408 MOVQ CX, 0x8(SP)
append.go:7 0x104e1cf 4889542410 MOVQ DX, 0x10(SP)
append.go:7 0x104e1d4 48895c2418 MOVQ BX, 0x18(SP)
append.go:7 0x104e1d9 4889742420 MOVQ SI, 0x20(SP)
append.go:7 0x104e1de e86d6bfeff CALL runtime.growslice(SB)
append.go:7 0x104e1e3 488b4c2428 MOVQ 0x28(SP), CX
append.go:7 0x104e1e8 488b442430 MOVQ 0x30(SP), AX
append.go:7 0x104e1ed 488b5c2438 MOVQ 0x38(SP), BX
append.go:7 0x104e1f2 488d7001 LEAQ 0x1(AX), SI
append.go:6 0x104e1f6 488b442440 MOVQ 0x40(SP), AX
append.go:7 0x104e1fb 488b542448 MOVQ 0x48(SP), DX
append.go:7 0x104e200 eb96 JMP 0x104e198
append.go:9 0x104e202 48898c2480000000 MOVQ CX, 0x80(SP)
append.go:9 0x104e20a 48895c2450 MOVQ BX, 0x50(SP)
append.go:9 0x104e20f e8cc44fdff CALL runtime.printlock(SB)

重点看这一行:

  append.go:7		0x104e1b5		4839de			CMPQ BX, SI
append.go:7 0x104e1b8 7ede JLE 0x104e198

BX里面存的是a现在的cap值,(可以从MOVL $0x5, BX看出来)。而SI里面存储的是老的slice的长度(DX)加1之后的值,就是新的slice需要的len值。所以上面两句的意思就是比较下新的len和cap的大小,如果len小于cap的话,就跳到0x104e198,就是直接执行MOVE操作,否则的话,就开始准备growslice。

总结

上面的分析说明,slice是否需要扩张的逻辑是编译器做的,并且编译器如果能直接判断是否这个slice需要扩张,就直接将是否需要扩张的结果作为编译结果。否则的话,就将这个if else的逻辑写在编译结果里面,在runtime时候跳转判断。

到这里我有点理解编译器和运行时的边界。其实本质上,两个步骤都是为了代码更快得出结果,编译器优化的越多,运行过程执行的速度就越快,当然编译器同时也需要兼顾生成的可执行文件的大小问题等。对一个语言,编译器优化,是个很重要的工作。

slice是什么时候决定要扩张?的更多相关文章

  1. 深度解密Go语言之Slice

    目录 当我们在说 slice 时,到底在说什么 slice 的创建 直接声明 字面量 make 截取 slice 和数组的区别在哪 append 到底做了什么 为什么 nil slice 可以直接 a ...

  2. Golang中的Slice与数组

    1.Golang中的数组 数组是一种具有固定长度的基本数据结构,在golang中与C语言一样数组一旦创建了它的长度就不允许改变,数组的空余位置用0填补,不允许数组越界. 数组的一些基本操作: 1.创建 ...

  3. [Golang]-1 Slice与数组的区别

    目录 数组 1.创建数组: 2.数组是值拷贝传递: 切片(slice) 1.首先看看slice的源码结构: 2.slice的创建: 3.slice使用make创建 4.切片作为参数传递 5.Golan ...

  4. Matlab slice方法和包络法绘制三维立体图

    前言:在地球物理勘探,流体空间分布等多种场景中,定位空间点P(x,y,x)的物理属性值Q,并绘制三维空间分布图,对我们洞察空间场景有十分重要的意义. 1. 三维立体图的基本要件: 全空间网格化 网格节 ...

  5. jQuery之常用且重要方法梳理(target,arguments,slice,substring,data,trigger,Attr)-(一)

    1.jquery  data(name) data() 方法向被选元素附加数据,或者从被选元素获取数据. $("#btn1").click(function(){ $(" ...

  6. js url.slice(star,end) url.lastIndexOf('/') + 1, -4

    var url = '"http://60.195.252.25:15518/20151228/XXSX/作三角形的高.mp4")' document.title = url.sl ...

  7. JavaScript中的slice,splice,substr,substring,split的区别

    万恶的输入法,在sublime中会显示出繁体字,各位看官见谅. 1.slice()方法:该方法在数组和string对象中都拥有. var a = [1,2,3,4,5,6]; var s = 'thi ...

  8. Max double slice sum 的解法

    1. 上题目: Task description A non-empty zero-indexed array A consisting of N integers is given. A tripl ...

  9. js中substr,substring,slice。截取字符串的区别

    substr(n1,n2) n1:起始位置(可以为负数) n2:截取长度(不可以为0,不可以为负数,可以为空) 当n1为正数时,从字符串的n1下标处截取字符串(起始位置),长度为n2. 当n1为负数时 ...

随机推荐

  1. webService(一)开篇

    Webservice技术在web开发中算是一个比较常见技术.这个对于大多数的web开发者,别管是Java程序员还是.NET程序员应该都不是很陌生.今天我就和大家一起来学习一下webservice的基本 ...

  2. 初探Apache Beam

    文章作者:luxianghao 文章来源:http://www.cnblogs.com/luxianghao/p/9010748.html  转载请注明,谢谢合作. 免责声明:文章内容仅代表个人观点, ...

  3. 印钞机 V1.0(量化选基总结)

    今年的元旦,在家把之前手工的选基方法完全程序化了.这是我的"印钞机" V1.0. 为什么叫印钞机,详细情况可见下文及最后的总结. 量化选基成果 我的主要基金投资方法其实就是量化选基 ...

  4. golang自定义路由控制实现(一)

        由于本人之前一直是Java Coder,在Java web开发中其实大家都很依赖框架,所以当在学习Golang的时候,自己便想着在Go开发中脱离框架,自己动手造框架来练习.通过学习借鉴Java ...

  5. TCP连接的建立与释放(三次握手与四次挥手)

    TCP连接的建立与释放(三次握手与四次挥手) TCP是面向连接的运输层协议,它提供可靠交付的.全双工的.面向字节流的点对点服务.HTTP协议便是基于TCP协议实现的.(虽然作为应用层协议,HTTP协议 ...

  6. ReenTrantLock可重入锁(和synchronized的区别)总结

    ReenTrantLock可重入锁(和synchronized的区别)总结 可重入性: 从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也 ...

  7. 深入理解Java NIO

    初识NIO: 在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存 ...

  8. 【网络】TCP/IP连接三次握手

    TCP握手协议 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确 ...

  9. 如何用Redis做LRU-Cache

    LRU(Least Recently Used)最近最少使用算法是众多置换算法中的一种. Redis中有一个maxmemory概念,主要是为了将使用的内存限定在一个固定的大小.Redis用到的LRU ...

  10. GROUP BY 和 ORDER BY一起使用时的注意点

    order by的列,必须是出现在group by子句里的列ORDER BY要在GROUP BY的后面