英文:
How to get memory size of variable in Go?
问题
我对map
和slice
的内存消耗很感兴趣,所以我写了一个程序来比较它们的大小。我使用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))
}
英文:
I am curious about the memory cost of map
and slice
, so I wrote a program to compare the sizes. I get the memory size by unsafe.Sizeof(s)
, but obviously it is wrong, because when I change the size, the output is the same.
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
得分: 31
unsafe.SizeOf()
和reflect.Type.Size()
只返回传递值的大小,而不会递归遍历数据结构并添加指向值的大小。
切片是一个相对简单的结构:reflect.SliceHeader
,由于我们知道它引用了一个支持数组,我们可以很容易地“手动”计算它的大小,例如:
s := make([]int32, 1000)
fmt.Println("[]int32的大小:", unsafe.Sizeof(s))
fmt.Println("[1000]int32的大小:", unsafe.Sizeof([1000]int32{}))
fmt.Println("s的实际大小:", unsafe.Sizeof(s)+unsafe.Sizeof([1000]int32{}))
输出结果(在Go Playground上尝试):
[]int32的大小:12
[1000]int32的大小:4000
s的实际大小:4012
映射是更复杂的数据结构,我不会详细介绍,但可以查看这个问题+答案:https://stackoverflow.com/questions/31847549/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。
英文:
unsafe.SizeOf()
and reflect.Type.Size()
only return the size of the passed value without recursively traversing the data structure and adding sizes of pointed values.
The slice is relatively a simple struct: reflect.SliceHeader
, and since we know it references a backing array, we can easily compute its size "manually", e.g.:
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{}))
Output (try it on the Go Playground):
Size of []int32: 12
Size of [1000]int32: 4000
Real size of s: 4012
Maps are a lot more complex data structures, I won't go into details, but check out this question+answer: https://stackoverflow.com/questions/31847549/golang-computing-the-memory-footprint-or-byte-length-of-a-map
Calculating size of any variable or structure (recursively)
If you want "real" numbers, you may take advantage of the testing tool of Go, which can also perform memory benchmarking. Pass the -benchmem
argument, and inside the benchmark function allocate only whose memory you want to measure:
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) }
}
(Remove the timing and printing calls from getSlice()
and getMap()
of course.)
Running with
go test -bench . -benchmem
Output is:
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
values tell you how many bytes were allocated per op. allocs/op
tells how many (distinct) memory allocations occurred per op.
On my 64-bit architecture (where the size of int
is 8 bytes) it tells that the size of a slice having 2000 elements is roughly 16 KB (in line with 2000 * 8 bytes). A map with 1000 int-int
pairs required approximately to allocate 42 KB.
答案2
得分: 16
这会产生一些编组开销,但我发现这是在运行时获取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
}
英文:
This incurs some marshaling overhead but I've found it's the simplest way during runtime to get the size of a value in go. For my needs the marshaling overhead wasn't a big issue so I went this route.
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
}
答案3
得分: 0
这是正确的方法,使用unsafe.Sizeof(s)
。只是对于给定的类型(整数、字符串等),结果将保持不变,而不考虑确切的值。
Sizeof接受任何类型的表达式x,并返回一个假设变量v的大小(以字节为单位),就好像通过var v = x声明了v一样。该大小不包括x可能引用的任何内存。例如,如果x是一个切片,Sizeof返回切片描述符的大小,而不是切片引用的内存的大小。
参考这里。
更新:
你可以使用编组(marshalling),然后使用Size()
比较值的字节表示。这只是将数据转换为字节字符串的问题。
英文:
This is the correct way, using unsafe.Sizeof(s)
. It's just that result will remain the same for a given type - integer, string, etc., disregarding the exact value.
> Sizeof takes an expression x of any type and returns the size in bytes of a hypothetical variable v as if v was declared via var v = x. The size does not include any memory possibly referenced by x. For instance, if x is a slice, Sizeof returns the size of the slice descriptor, not the size of the memory referenced by the slice.
Reference here.
Update:
You can use marshalling and then compare values representations in bytes with Size()
. It's only matter of converting a data to a byte string.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论