赞
踩
需要处理的对象非常多时,比如:时序数据库victoriametrics源码中,利用了mmap申请内存并自己维护,从而避免过多gc影响性能,因为频繁申请和释放堆对象会降低性能
参考victoriametrics malloc_mmap.go
package main import ( "flag" "fmt" "runtime" "runtime/debug" "time" "golang.org/x/sys/unix" ) const size = 3 * 1024 * 1024 * 1024 // 3GB const hello = "hello" func allocateMemory(useMmap bool) []byte { if useMmap { data, err := unix.Mmap(-1, 0, int(size), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE) if err != nil { fmt.Println("Error creating memory mapping:", err) return nil } // 将 "hello" 写入满整个内存 for i := 0; i < size; i += len(hello) { copy(data[i:min(i+len(hello), size)], []byte(hello)) } return data } // 使用 make 分配内存并写入满整个内存 data := make([]byte, size) for i := 0; i < size; i += len(hello) { copy(data[i:min(i+len(hello), size)], []byte(hello)) } return data } func min(a, b int) int { if a < b { return a } return b } // 打印 GC 统计信息和当前内存情况 func printGCStatsAndMemoryInfo(label string, data []byte) { var stats debug.GCStats debug.ReadGCStats(&stats) var memStats runtime.MemStats runtime.ReadMemStats(&memStats) fmt.Printf("%s GC Stats: LastGC=%v, NumGC=%d, PauseTotal=%v\n", label, stats.LastGC, stats.NumGC, stats.PauseTotal) fmt.Printf("%s Memory Stats: Alloc=%v, TotalAlloc=%v, Sys=%v, NumGC=%v\n", label, memStats.Alloc, memStats.TotalAlloc, memStats.Sys, memStats.NumGC) fmt.Printf("%s Current Memory: %v bytes\n", label, len(data)) } func main() { useMmap := flag.Bool("usemmap", false, "Specify whether to use mmap for memory allocation (default: false)") flag.Parse() if *useMmap { fmt.Println("Using mmap for memory allocation") } else { fmt.Println("Not using mmap for memory allocation") } // 定期打印 GC 执行情况和当前内存情况 ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() data := allocateMemory(*useMmap) defer func() { if *useMmap { unix.Munmap(data) } }() for { select { case <-ticker.C: // 每隔2秒打印一次 printGCStatsAndMemoryInfo("Current", data) } } }
使用go的堆内存:看到当前的内存占用了3G,且go的gc感知到这部分的,所以gc需要扫的内存是很多的,而这部分是常驻的话又不会被回收,所以导致不必要的gc压力
[root@ ~]# ./gc-test
Not using mmap for memory allocation
Current GC Stats: LastGC=2024-01-29 15:10:45.747195763 +0800 CST, NumGC=1, PauseTotal=185.526µs
Current Memory Stats: Alloc=3221337272, TotalAlloc=3221338128, Sys=3283418896, NumGC=1
Current Current Memory: 3221225472 bytes
Current GC Stats: LastGC=2024-01-29 15:10:45.747195763 +0800 CST, NumGC=1, PauseTotal=185.526µs
Current Memory Stats: Alloc=3221355080, TotalAlloc=3221355936, Sys=3283418896, NumGC=1
Current Current Memory: 3221225472 bytes
...
...
使用top看其实都占用了3G:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
319166 root 20 0 4425716 3.1g 1320 S 0.0 19.9 0:05.94 gc-test
使用mmap:看到虽然当前的内存占用了3G,但是go的gc感知不到这部分的,所以gc需要扫的内存是很少的
[root@ ~]# ./gc-test -usemmap
Using mmap for memory allocation
Current GC Stats: LastGC=1970-01-01 08:00:00 +0800 CST, NumGC=0, PauseTotal=0s
Current Memory Stats: Alloc=105856, TotalAlloc=105856, Sys=7183376, NumGC=0
Current Current Memory: 3221225472 bytes
Current GC Stats: LastGC=1970-01-01 08:00:00 +0800 CST, NumGC=0, PauseTotal=0s
Current Memory Stats: Alloc=122184, TotalAlloc=122184, Sys=7183376, NumGC=0
Current Current Memory: 3221225472 bytes
...
...
使用top看其实都占用了3G:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
319179 root 20 0 4372212 3.0g 1320 S 0.0 19.6 0:05.23 gc-test
结论:虽然在go堆内分配内存和使用mmap分配内存在进程外的视角是一样的,但是在go runtime视角上,是看不到mmap这部分的,所以不会去进行gc扫描的,可以减轻压力
所以对于常驻大内存使用mmap性能更高,但是要自己维护好分配和释放,否则会导致内存泄漏
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。