如何在 Go 中获取变量的内存大小?

Posted

技术标签:

【中文标题】如何在 Go 中获取变量的内存大小?【英文标题】:How to get memory size of variable in Go? 【发布时间】:2017-10-30 15:36:10 【问题描述】:

我很好奇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))

【问题讨论】:

没有需要这样做,因为您可以计算所需的空间:将切片的上限乘以每个元素的大小。对于地图来说,由于某些内部无法访问(例如哈希冲突),因此更加困难,但它基本上是相同的。 【参考方案1】:

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

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

参考here。

更新:

您可以使用编组,然后将值表示以字节为单位与Size() 进行比较。只需将数据转换为字节字符串即可。

【讨论】:

那么我怎样才能在我的情况下获得真正的尺寸? 这会产生编组开销。想知道是否有办法在不编组的情况下做到这一点。【参考方案2】:

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: computing the memory footprint (or byte length) of a map

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

如果您想要“真实”数字,您可以利用 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) 

(当然,从getSlice()getMap() 中删除计时和打印调用。)

运行

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。

【讨论】:

【参考方案3】:

这会产生一些编组开销,但我发现这是在运行时获取 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

【讨论】:

以上是关于如何在 Go 中获取变量的内存大小?的主要内容,如果未能解决你的问题,请参考以下文章

C/C++ 变量申请内存 怎么获取变量的空间大小 像 int *i = (int*)malloc(100); 获取100的大小 谢谢

golang-指针类型

go 指针类型

Go笔记-指针

php获取变量所占内存大小的方法

python 获取变量的内存大小