V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
chesha1
V2EX  ›  Go 编程语言

go 的内存使用疑问

  •  
  •   chesha1 · 19 天前 · 1990 次点击

    下面这段代码:

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"unique"
    )
    
    func printMemUsage() {
    	var m runtime.MemStats
    	runtime.ReadMemStats(&m)
    
    	fmt.Printf("Alloc = %v B", m.Alloc)
    	fmt.Printf("\tTotalAlloc = %v B", m.TotalAlloc)
    	fmt.Printf("\n")
    }
    
    type TestStruct struct {
    	A float64
    	B float64
    	C float64
    	D float64
    }
    
    func main() {
    	testobj1 := TestStruct{A: 1, B: 2, C: 3, D: 4}
    	printMemUsage()
    
    	testobj2 := TestStruct{A: 1, B: 2, C: 3, D: 4}
    	printMemUsage()
    
    	testobj3 := TestStruct{A: 1, B: 2, C: 3, D: 4}
    	printMemUsage()
    
    	testobj4 := TestStruct{A: 1, B: 2, C: 3, D: 4}
    	printMemUsage()
    	fmt.Println(testobj1, testobj2, testobj3, testobj4)
    	fmt.Println("---------")
    
    	uniqueobj1 := unique.Make(TestStruct{A: 1, B: 2, C: 3, D: 4})
    	printMemUsage()
    
    	uniqueobj2 := unique.Make(TestStruct{A: 1, B: 2, C: 3, D: 4})
    	printMemUsage()
    
    	uniqueobj3 := unique.Make(TestStruct{A: 1, B: 2, C: 3, D: 4})
    	printMemUsage()
    
    	uniqueobj4 := unique.Make(TestStruct{A: 1, B: 2, C: 3, D: 4})
    	printMemUsage()
    	fmt.Println(uniqueobj1, uniqueobj2, uniqueobj3, uniqueobj4)
    	fmt.Println("---------")
    
    	testobj5 := TestStruct{A: 1, B: 2, C: 3, D: 4}
    	printMemUsage()
    
    	testobj6 := TestStruct{A: 1, B: 2, C: 3, D: 4}
    	printMemUsage()
    
    	testobj7 := TestStruct{A: 1, B: 2, C: 3, D: 4}
    	printMemUsage()
    
    	testobj8 := TestStruct{A: 1, B: 2, C: 3, D: 4}
    	printMemUsage()
    	fmt.Println(&testobj5, &testobj6, &testobj7, &testobj8)
    
    }
    

    输出结果是:

    Alloc = 118256 B        TotalAlloc = 118256 B
    Alloc = 119704 B        TotalAlloc = 119704 B
    Alloc = 119720 B        TotalAlloc = 119720 B
    Alloc = 119736 B        TotalAlloc = 119736 B
    {1 2 3 4} {1 2 3 4} {1 2 3 4} {1 2 3 4}
    ---------
    Alloc = 121056 B        TotalAlloc = 121056 B
    Alloc = 121072 B        TotalAlloc = 121072 B
    Alloc = 121088 B        TotalAlloc = 121088 B
    Alloc = 121104 B        TotalAlloc = 121104 B
    {0x1400001e180} {0x1400001e180} {0x1400001e180} {0x1400001e180}
    ---------
    Alloc = 121152 B        TotalAlloc = 121152 B
    Alloc = 121200 B        TotalAlloc = 121200 B
    Alloc = 121248 B        TotalAlloc = 121248 B
    Alloc = 121296 B        TotalAlloc = 121296 B
    &{1 2 3 4} &{1 2 3 4} &{1 2 3 4} &{1 2 3 4}
    

    本来是想试一下 unique 包对于内存的节约效果的,但是没想到第一种写法,每次也只增加了 16B ,和用了 unique 效果一样

    而第三种写法,和第一种相比,确实每次用的内存多了,但是为什么后面的 fmt.Println 的写法区别,能影响前面的内存使用量

    版本:go 1.23.0

    平台:arm macos

    第 1 条附言  ·  19 天前

    上面这段代码有点陷阱,我自己回去又捣鼓了一下

    这实际上是两个问题的杂糅,一个是unique包的使用,先不谈

    第二个,下面是一个最小化的实例,除了第一次,后面每次fmt.Printf都会增加16B的内存,请问是什么导致的:

    package main
    
    import (
    	"fmt"
    	"runtime"
    )
    
    func main() {
    	var m runtime.MemStats
    	for i := 0; i < 10; i++ {
    		runtime.ReadMemStats(&m)
    		fmt.Printf("Alloc = %v B\n", m.Alloc)
    	}
    }
    

    关于逃逸,用go build -gcflags="-m" main.go看了一下,确实在print这一行

    ./main.go:82:13: inlining call to fmt.Printf
    ./main.go:82:13: ... argument does not escape
    ./main.go:82:33: m.Alloc escapes to heap
    

    但是又查了一番,没有资料提到这个16B是什么内容

    6 条回复    2024-08-28 15:58:23 +08:00
    matrix1010
        1
    matrix1010  
       19 天前   ❤️ 1
    你有没有注意到 TestStruct 其实是 32 bytes 的,那 Alloc 应该以 32 为单位增加。但你这里是 16 bytes 。有一个东西恰好是 16 bytes: reflect.StringHeader
    whoami9894
        2
    whoami9894  
       19 天前   ❤️ 1
    1. 栈上的变量编译后就是一个 `sub rsp, N`,内存用量不会变的
    2. fmt.Print 内部有一个内存池
    3. 打印结构体指针需要先反射判断是指针,打印一个 `&`,再递归打印结构体
    xiaozirun
        3
    xiaozirun  
       19 天前   ❤️ 1
    unique 包对内存使用有优化效果吗,官方文档似乎只说了,他是只是用实现快速比等。
    文档: https://pkg.go.dev/unique
    picone
        4
    picone  
       19 天前   ❤️ 1
    @xiaozirun 有优化的,实际上他就是 Java 的 string intern ,只不过他支持泛型而已。

    https://github.com/golang/go/issues/62483
    leonshaw
        5
    leonshaw  
       19 天前   ❤️ 1
    第一个在栈上的,第三个逃逸了
    xiaozirun
        6
    xiaozirun  
       19 天前
    @picone 好吧确实 为每个值返回一个全局唯一标识,同时这个值会被保存在池中
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   950 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 19:43 · PVG 03:43 · LAX 12:43 · JFK 15:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.