什么逃逸到堆中?

问题描述 投票:0回答:1

我有这段代码,本来应该根本不进行分配,但由于某种原因它确实进行了分配。正如基准测试所说,发生了 2 次分配/操作。

函数的哪些行进行分配?为什么?

功能:

func (vi *VarInt /* int32 */) Read(input io.Reader) error {
    var (
        b     byte
        buf   = unsafe.Slice(&b, 1)
        shift int
        value uint32
    )
    for {
        _, err := io.ReadFull(input, buf)
        if err != nil {
            return err
        }

        value |= (uint32(b) & 0b01111111) << shift

        if (b & 0b10000000) == 0 {
            *vi = VarInt(value)
            return nil
        }

        shift += 7
        if shift >= 32 {
            return ErrVarIntTooLong
        }
    }
}

func (vi *VarInt /* int32 */) Write(output io.Writer) error {
    var (
        varint [5]byte
        uvalue = uint32(*vi)
        x      int
    )
    for ; ; x++ {
        vb := uint8(uvalue)

        if (vb & 0b10000000) == 0 {
            varint[x] = vb
            break
        }

        varint[x] = (vb & 0b01111111) | 0b10000000

        uvalue >>= 7
    }

    _, err := output.Write(varint[:x+1])
    if err != nil {
        return err
    }

    return nil
}

基准:

func BenchmarkVarInt(b *testing.B) {
    var buf bytes.Buffer
    buf.Grow(5)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        vi := (VarInt)(i)
        vi.Write(&buf)
        vi.Read(&buf)
        buf.Reset()
    }
}

我想

buf
切片以某种方式逃逸,但我不知道如何逃脱,因为据我了解,在这种情况下切片是在堆栈上分配的结构,它将指向变量
b
作为其数据。我尝试将表达式
unsafe.Slice(&b, 1)
更改为
(*[1]byte)(unsafe.Pointer(&b))[:]
,但没有任何改变。

go memory-management unsafe unsafe-pointers
1个回答
0
投票

当一个值被装箱在接口中时,它总是被认为是逃逸的——即使该值从未在调用堆栈之外使用过,Go 也会在此时停止分析并认为有人可能已经掌握了该地址,并且因此该值必须位于堆上。

由于

Read
需要
io.Reader
并且
Write
需要
io.Writer
,所以
buf
(这是传递给这两个函数的
bytes.Buffer
)必须转义。

即使您使这些函数采用具体类型

bytes.Buffer
(您可能不想要),但这还不够,因为
Read
调用
io.ReadFull
,后者又采用
io.Reader
。你必须比这更努力地工作才能使这种分配免于分配。

作为旁注,对于

Read
中的其他问题有一个更简单的解决方案,不需要任何
unsafe.Slice
恶作剧:只需将
var b byte
替换为
var b [1]byte
(在内存中完全相同) ,将
b[:]
传递给
ReadFull
,然后在您使用
b[0]
的其他地方使用
b

© www.soinside.com 2019 - 2024. All rights reserved.