V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
BBCCBB
V2EX  ›  程序员

golang WaitGroup 中用到的内存对齐方式会受到 gc 的影响吗?

  •  1
     
  •   BBCCBB · 314 天前 · 1509 次点击
    这是一个创建于 314 天前的主题,其中的信息可能已经有所发展或是发生改变。

    RT

    go WaitGroup 中有这样的代码:

    type WaitGroup struct {
    	noCopy noCopy
    
    	// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
    	// 64-bit atomic operations require 64-bit alignment, but 32-bit
    	// compilers do not ensure it. So we allocate 12 bytes and then use
    	// the aligned 8 bytes in them as state, and the other 4 as storage
    	// for the sema.
    	state1 [3]uint32
    }
    
    
    func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {  
     if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {  
      return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]  
     } else {  
      return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]  
     }  
    }  
    

    这个 uintptr(unsafe.Pointer(&wg.state1))会不会受到 gc 的影响, 比如在 gc 后地址就变了..

    变成%8==4 了.. (包括在 32 位对齐的机器上是否有地址改变这种问题) ??

    有知道的大佬多说说 🐶

    相关知识链接(随便找个): https://www.helloworld.net/p/8378277893

    17 条回复    2021-09-30 10:26:08 +08:00
    BBCCBB
        1
    BBCCBB  
    OP
       314 天前
    ```go
    type Wg struct {

    state1 [3]uint32
    }

    func main() {
    var wg Wg
    fmt.Println(uintptr(unsafe.Pointer(&wg.state1)) % 8)
    }
    ```

    64 位 mac 上执行这个代码多次, 是会出现 0 和 4 两种结果的
    BBCCBB
        2
    BBCCBB  
    OP
       314 天前
    64 位对齐不是说数据的地址是 8 的整数倍吗... 那为啥这里还有 0 和 4 两种结果?
    katsusan
        3
    katsusan  
       314 天前
    @BBCCBB #1 加一个 fmt.Println(wg)使其在堆上分配
    BBCCBB
        4
    BBCCBB  
    OP
       314 天前
    @katsusan 这是个啥神奇的操作, 怪异但有效.... 离谱
    Huelse
        5
    Huelse  
       313 天前
    the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed.

    https://pkg.go.dev/unsafe#Pointer
    katsusan
        6
    katsusan  
       313 天前
    > 64 位对齐不是说数据的地址是 8 的整数倍吗... 那为啥这里还有 0 和 4 两种结果?

    1 楼例子里的 Wg 是 4 字节对齐的,unsafe.Alignof(wg)或 reflect.TypeOf(wg).Align()可以看到。

    > 64 位 mac 上执行这个代码多次, 是会出现 0 和 4 两种结果的

    wg 分配在栈上,受 runtime 申请栈空间时获得的地址影响,不一定保证 8 字节对齐还是只满足 4 字节对齐。

    > 加一个 fmt.Println(wg)使其在堆上分配

    加 fmt.Println 让 wg 分配在堆上利用了 tiny allocator 获得的 tiny block 地址在 64 位环境下为 8 字节对齐,
    不代表内存分配器一定会给 wg 返回 8 字节对齐的地址,比如下面的代码里 wg 的地址应该只满足 4 字节对齐。

    ```Go
    type Wg struct {

    state1 [3]uint32
    }

    func main() {
    var b bool
    var wg Wg
    fmt.Printf("%p\n", &b)
    fmt.Printf("%p\n", &wg)
    }
    ```
    cholerae
        7
    cholerae  
       313 天前
    "gc 后地址就变了" 是啥意思? go 没有移动 gc
    BBCCBB
        8
    BBCCBB  
    OP
       313 天前
    @cholerae 就是 gc 后一个 waitGroup 对象的地址就变了.
    在 jvm 里, gc 是会移动内存的.
    BBCCBB
        9
    BBCCBB  
    OP
       313 天前
    @katsusan
    > 64 位对齐不是说数据的地址是 8 的整数倍吗... 那为啥这里还有 0 和 4 两种结果?
    这个看下来只要地址是 unsafe.AlignOf(wg)的 整数倍就行了? 64 位机器上没要求对象地址必须是 8 的整数倍?


    gc 对象位置在内存里被移动 这个问题大佬知道吗?
    katsusan
        10
    katsusan  
       313 天前
    @BBCCBB #9
    > 64 位机器上没要求对象地址必须是 8 的整数倍?
    除了一些特别的操作比如 atomic 外,没有强制。但地址对齐对访问内存性能有影响,非对齐地址会导致需要拆分成两次内存访问。在 amd64 下,对于 byte 型数据需要 1 字节对齐,word 需要 2 字节,doubleword 需要 4 字节,quadword 需要 8 字节对齐。

    > gc 对象位置在内存里被移动
    我看了下 JAVA 的各式 GC 算法里普遍对 young generation 使用 mark-copy 算法,对 old generation 使用 mark-sweep 算法。
    你说的移动指的是 copy 过程中的移动吧,Golang 里未采用分代 GC,只有 mark-sweep 。
    BBCCBB
        11
    BBCCBB  
    OP
       313 天前
    @katsusan jvm 老年代里的 gc 有 mark-sweep, 也有 mark-sweep & compact, 压缩, 防止内存碎片用的,

    go 里只有标记清除, 没有整理内存, 压缩这个操作是不?
    BBCCBB
        12
    BBCCBB  
    OP
       313 天前
    @katsusan 对的, 我说的就是压缩内存, 防止内存碎片过程中移动内存这个操作,
    BBCCBB
        13
    BBCCBB  
    OP
       313 天前
    @katsusan 简单看了下 go 的 gc, 没有 compact 操作, 内存都被 tmolloc 这种分配器切成一块一块的..
    BBCCBB
        14
    BBCCBB  
    OP
       313 天前
    @katsusan 我还看到一个概念


    The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.

    变量或开辟的结构体、数组和切片值中的第一个 64 位字可以被认为是 8 字节对齐
    这一句中开辟的意思是通过声明,make,new 方式创建的,就是说这样创建的 64 位字可以保证是 64 位对齐的。
    BBCCBB
        15
    BBCCBB  
    OP
       313 天前
    地址按照这个规则来对齐

    uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
    my3157
        16
    my3157  
       313 天前 via Android
    @BBCCBB #4
    > 这是个啥神奇的操作, 怪异但有效.... 离谱

    加了 println 逃逸了
    go build -gcflags '-m -m -l' main.go
    BBCCBB
        17
    BBCCBB  
    OP
       311 天前
    @my3157 蟹蟹, 我已经悟了,

    type Wg struct {

    state1 [3]uint32
    }

    这个本来就是 32 位对齐的, 因为按下面这个规则来对齐的,

    uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0


    所以出现 0/4 是对的.
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1157 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 23:11 · PVG 07:11 · LAX 16:11 · JFK 19:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.