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. 拖拽模块move2

    之前的模块代码太死板了,由于内部定义了控件的ID,使用起来很不方便,so-----直接看代码 <script> var move =(function(){ function drop(b ...

  2. UE4学习心得:Scene Component蓝图的一个简单应用

    Scene Component是蓝图类中一个不怎么常用的分类(特别是对于新手而言),主要是其实现的功能可以在Actor类中用相同的方法实现,使其作用显得有点多余. 笔者在使用过这个类之后发现其作用更相 ...

  3. 想要薪资20-30K,Python程序员认真敲代码就够了!

    在这个年代,互联网的飞速壮大大家有目共睹,除了表露出的公共受益,其中计算机编程者也是做出了巨大的贡献,即使外国编程语言仍旧属于领导者,但是在互联网的壮大下,我们这是"地球村",国内 ...

  4. 使用XHProf分析PHP性能瓶颈(二)

    上一篇文章里,我们介绍了如何基于xhprof扩展来分析PHP性能,并记录到日志里,最后使用xhprof扩展自带的UI在web里展示出来.本篇文章将讲述2个知识点: 使用xhgui代替xhprof的默认 ...

  5. Java 面试知识点解析(三)——JVM篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  6. Java 的字节流文件读取(一)

    上篇文章我们介绍了抽象化磁盘文件的 File 类型,它仅仅用于抽象化描述一个磁盘文件或目录,却不具备访问和修改一个文件内容的能力. Java 的 IO 流就是用于读写文件内容的一种设计,它能完成将磁盘 ...

  7. PAT1040:Longest Symmetric String

    1040. Longest Symmetric String (25) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, ...

  8. 数码相框(LCD、I2C)

    一:项目介绍    该项目最终实现的功能很简单,手指在触摸屏左滑(下一张图片),右滑(上一张图片)    1.1软硬件资源    硬件:pc机,ARM Cortex-A9开发板    软件:linux ...

  9. Vue数据双向绑定原理及简单实现

    嘿,Goodgirl and GoodBoy,点进来了就看完点个赞再go. Vue这个框架就不简单介绍了,它最大的特性就是数据的双向绑定以及虚拟dom.核心就是用数据来驱动视图层的改变.先看一段代码. ...

  10. VirtualBox不能为虚拟电脑打开一个新任务——The VirtualBox kernel modules do not match this version of VirtualBox

    本文由荒原之梦原创,原文链接:http://zhaokaifeng.com/?p=608 一.问题产生的环境 物理机操作系统:Ubuntu 17.10 (Ubuntu版本查看命令: cat /etc/ ...