问题回顾

gdb
  • 请查看下面一段代码,会输出什么,为什么?
    • A: 5 8 B: 8 8 C: 5 5 D: 5 6
package main

import (
    "fmt"
)

func main() {
    s := []int{1, 2}
    s = append(s, 3, 4, 5)
    fmt.Println(len(s), cap(s))
}
s2s30x587450

slice 扩容算法是否遗漏了什么?

首先我们回顾一下上一篇文章当中讲到的 slice 扩容的 算法:

  • 如果需要的最小容量比两倍原有容量大,那么就取需要的容量
  • 如果原有 slice 长度小于 1024 那么每次就扩容为原来的两倍
  • 如果原 slice 大于等于 1024 那么每次扩容就扩为原来的 1.25 倍

按照这个逻辑进行套用:

slen=2s = append(s, 3, 4, 5)5 > 2*2C: 5 5D: 5 6

接下来我们就使用 gdb 调试一下看看结果

使用 GDB 调试 Golang 代码

VS CodeGoland

安装

brew install gdb # for mac

如果是 linux 使用自带的包管理工具进行安装即可
注意安装完成之后需要在 home 目录上添加相关配置

vim ~/.gdbinit

# 输入下面
add-auto-load-safe-path $GOROOT/src/runtime/runtime-gdb.py # 这里将 $GOROOT 替换为你的 GO 安装目录

如果你是 mac 首次安装 gdb 需要给 gdb 签名,可以参考: https://gist.github.com/hlissner/898b7dfc0a3b63824a70e15cd0180154

编译代码

-gcflags=all="-N -l"-ldflags='-compressdwarf=false'No symbol table is loaded. Use the "file" command.-ldflags='-compressdwarf=false'
go build -o bin/03_q1_slice_cap -gcflags=all="-N -l" -ldflags='-compressdwarf=false' 02_array/03_q1_slice_cap/main.go

进入 GDB 调试窗口

-tui
gdb -tui ./bin/03_q1_slice_cap
Loading Go Runtime support.

常用调试命令

b 文件名:行数info brcsnuntiluntil:行号info localsinfo argsinfo goroutineshelpq

调试

知道如何操作之后,我们开始干活

b main.go:11rslices
ninfo localsnewcapn
newcapcapmem=48PtrSize=8newcap


知道问题出现在哪里之后我们可以再来看一下源代码,有一个内存对齐的函数

// Returns size of the memory block that mallocgc will allocate if you ask for the size.
func roundupsize(size uintptr) uintptr {
    if size < _MaxSmallSize {
        if size <= smallSizeMax-8 {
            return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
        } else {
            return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])
        }
    }
    if size+_PageSize < size {
        return size
    }
    return alignUp(size, _PageSize)
}

const (
    _MaxSmallSize   = 32768
    smallSizeDiv    = 8
    smallSizeMax    = 1024
    largeSizeDiv    = 128
    _NumSizeClasses = 67
    _PageShift      = 13
)

var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, ...}
var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, ...}
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)5*8=4048

总结

slice 扩容算法

  • 如果需要的最小容量比两倍原有容量大,那么就取需要的容量
  • 如果原有 slice 长度小于 1024 那么每次就扩容为原来的两倍
  • 如果原 slice 大于等于 1024 那么每次扩容就扩为原来的 1.25 倍
  • 除此之外扩容容量计算完成之后,还会进行一次内存对齐操作

搜索 slice 扩容策略很多都会说第 2、3 点但是没有说 1, 4 点就会造成一些困惑

GDB 调试

  • mac 下安装使用 gdb 比较麻烦,建议使用 linux
  • 一般情况下我们还是建议使用 dlv 进行调试,如果有 runtime 库的调试需求可以使用 gdb
  • 学习了 gdb 的安装以及基本使用方式,你之前有类似的经历么?一般会在什么情况下使用 gdb 进行调试
s2s30x587450
makeslicemallocgc
if size == 0 {
    return unsafe.Pointer(&zerobase)
}