如何获取变量的总引用内存?

24
我对mapslice的内存消耗很好奇,所以我写了一个程序来比较它们的大小。我通过unsafe.Sizeof(s)来获取内存大小,但显然是错误的,因为当我改变大小时,输出结果是相同的。
func getSlice(size int) []int {
    t := time.Now()
    s := make([]int, size*2)
    for i := 0; i < size; i++ {
        index := i << 1
        s[index] = i
        s[index+1] = i
    }
    fmt.Println("slice time cost: ", time.Since(t))
    return s
}

func getMap(size int) map[int]int {
    t := time.Now()
    m := make(map[int]int, size)
    for i := 0; i < size; i++ {
        m[i] = i
    }
    fmt.Println("map time cost: ", time.Since(t))
    return m
}

func TestMem(t *testing.T) {
    size := 1000
    s := getSlice(size)
    m := getMap(size)
    fmt.Printf("slice size: %d\n", unsafe.Sizeof(s))
    fmt.Printf("map size: %d\n", unsafe.Sizeof(m))
}

不需要这样做,因为您可以计算所需的空间:将切片的容量乘以每个元素的大小。对于映射,由于某些内部不可访问(例如哈希冲突),因此更加困难,但基本上是相同的。 - Volker
3个回答

31
unsafe.SizeOf()reflect.Type.Size() 只会返回传递值的大小,而不会递归遍历数据结构并添加指向值的大小。
切片是一个相对简单的结构:reflect.SliceHeader,由于我们知道它引用了一个后备数组,因此我们可以通过手动计算其大小来轻松获得其大小,例如:
s := make([]int32, 1000)

fmt.Println("Size of []int32:", unsafe.Sizeof(s))
fmt.Println("Size of [1000]int32:", unsafe.Sizeof([1000]int32{}))
fmt.Println("Real size of s:", unsafe.Sizeof(s)+unsafe.Sizeof([1000]int32{}))

输出结果(请在Go Playground上尝试):

Size of []int32: 12
Size of [1000]int32: 4000
Real size of s: 4012

地图是更为复杂的数据结构,我不会深入讲解,但可以查看这个问题和答案:Golang: 计算地图的内存占用(或字节长度)

递归计算任何变量或结构的大小

如果您想要真实的数字,可以利用Go的测试工具,它还可以执行内存基准测试。传递-benchmem参数,并在基准函数中仅分配要测量的内存:

func BenchmarkSlice100(b *testing.B) {
    for i := 0; i < b.N; i++ { getSlice(100) }
}
func BenchmarkSlice1000(b *testing.B) {
    for i := 0; i < b.N; i++ { getSlice(1000) }
}
func BenchmarkSlice10000(b *testing.B) {
    for i := 0; i < b.N; i++ { getSlice(10000) }
}
func BenchmarkMap100(b *testing.B) {
    for i := 0; i < b.N; i++ { getMap(100) }
}
func BenchmarkMap1000(b *testing.B) {
    for i := 0; i < b.N; i++ { getMap(1000) }
}
func BenchmarkMap10000(b *testing.B) {
    for i := 0; i < b.N; i++ { getMap(10000) }
}
< p >(当然,要从< code > getSlice() 和< code > getMap() 中删除计时和打印调用。) < p >运行中
go test -bench . -benchmem

输出结果为:

BenchmarkSlice100-4    3000000        471 ns/op        1792 B/op      1 allocs/op
BenchmarkSlice1000-4    300000       3944 ns/op       16384 B/op      1 allocs/op
BenchmarkSlice10000-4    50000      39293 ns/op      163840 B/op      1 allocs/op
BenchmarkMap100-4       200000      11651 ns/op        2843 B/op      9 allocs/op
BenchmarkMap1000-4       10000     111040 ns/op       41823 B/op     12 allocs/op
BenchmarkMap10000-4       1000    1152011 ns/op      315450 B/op    135 allocs/op

B/op 的值告诉您每个操作分配了多少字节。 allocs/op 告诉您每个操作发生了多少(不同的)内存分配。

在我的 64 位架构上(其中int的大小为 8 字节),它告诉我们拥有 2000 个元素的切片的大小大约为 16 KB(与 2000 * 8 字节一致)。具有 1000 个int-int对的映射需要大约分配 42 KB。


17

这会产生一些编组开销,但我发现这是在运行时获取Go语言值大小的最简单方法。对于我的需求,编组开销不是一个大问题,因此我选择了这种方法。

func getRealSizeOf(v interface{}) (int, error) {
    b := new(bytes.Buffer)
    if err := gob.NewEncoder(b).Encode(v); err != nil {
        return 0, err
    }
    return b.Len(), nil
}

0
这是正确的方法,使用unsafe.Sizeof(s)。对于给定类型 - 整数、字符串等,结果将保持不变,而不考虑确切的值。

Sizeof接受任何类型的表达式x,并返回一个假设变量v通过var v = x声明的大小(以字节为单位)。该大小不包括x可能引用的任何内存。例如,如果x是一个片段,则Sizeof返回片段描述符的大小,而不是片段引用的内存的大小。

参考此处更新: 您可以使用编组,然后使用Size()比较字节中的值表示。这只涉及将数据转换为字节字符串。

那么在我的情况下,我该如何获得真实的大小? - roger
这将产生编组开销。不知道有没有一种方法可以在不进行编组的情况下完成它。 - Sush

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接