go 运行时:在darwin系统上,垃圾回收器没有释放所有可能的内存,

oogrdqng  于 7个月前  发布在  Go
关注(0)|答案(1)|浏览(51)

这实际上是对 #29844 的后续。我试图在内存受限的 iOS 上减少我们的内存使用。
在 darwin 上,sysUnused 调用 madvise(v, n, _MADV_FREE_REUSABLE),将这些页面标记为操作系统可回收。然而,出乎意料的是,它并没有将所有页面标记为可回收。我不知道为什么,但这里有一个方法可以重现它。
以下程序创建并释放一个单一的大字节切片。它会在三个暂停点:分配前、分配后和分配已释放时暂停。

package main

import (
	"runtime/debug"
	"time"
)

var b []byte

func main() {
	// Call time.Sleep and debug.FreeOSMemory once up front,
	// so that all basic runtime structures get set up
	// and all relevant pages get dirtied.
	time.Sleep(time.Millisecond)
	debug.FreeOSMemory()
	println("start")
	time.Sleep(5 * time.Second)

	b = make([]byte, 4_000_000)
	for i := range b {
		b[i] = 1
	}
	println("allocated")
	time.Sleep(5 * time.Second)

	b = nil
	debug.FreeOSMemory()
	time.Sleep(3 * time.Second) // wait for the scavenger's effects to be visible
	println("freed")
	time.Sleep(3 * time.Hour)
}

在 macOS 上运行此程序,我使用 footprint 测量应用程序的占用空间,使用 vmmap 获取内存使用详细信息,在每个暂停点进行测量。具体来说,我运行 go build -o jjj x.go && GODEBUG=allocfreetrace=1 ./jjj 来运行它,然后运行 footprint jjj && vmmap -pages -interleaved -submap jjj 来测量它。
分配前的 Go 堆报告:

Dirty      Clean  Reclaimable    Regions    Category
    ---        ---          ---        ---    ---
1168 KB        0 B          0 B         38    untagged ("VM_ALLOCATE")

分配后:

Dirty      Clean  Reclaimable    Regions    Category
    ---        ---          ---        ---    ---
5344 KB        0 B          0 B         39    untagged ("VM_ALLOCATE")

释放后:

Dirty      Clean  Reclaimable    Regions    Category
    ---        ---          ---        ---    ---
4192 KB        0 B      1344 KB         40    untagged ("VM_ALLOCATE")

请注意,4192KB-1344KB=2848KB,这比我们开始的 1168KB 要高得多。(确切的数字会因运行而略有不同。)
我们可以使用 vmmap (带有上述标志)窥探会计细节。对于 Go 堆,分配前:

REGION TYPE                    START - END         [   VSIZE    RSDNT    DIRTY     SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
VM_ALLOCATE               14000000000-14000400000  [  256       40       40        0   ] rw-/rwx SM=ZER  
VM_ALLOCATE               14000400000-14004000000  [ 3840        0        0        0   ] ---/rwx SM=NUL

分配后:

REGION TYPE                    START - END         [   VSIZE    RSDNT    DIRTY     SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
VM_ALLOCATE               14000000000-14000400000  [  256      200      200        0   ] rw-/rwx SM=ZER  
VM_ALLOCATE               14000400000-14000800000  [  256       85       85        0   ] rw-/rwx SM=PRV  
VM_ALLOCATE               14000800000-14004000000  [ 3584        0        0        0   ] ---/rwx SM=NUL

释放后:

REGION TYPE                    START - END         [   VSIZE    RSDNT    DIRTY     SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
VM_ALLOCATE               14000000000-14000400000  [  256      202      202        0   ] rw-/rwx SM=ZER  
VM_ALLOCATE               14000400000-14000800000  [  256       85        1        0   ] rw-/rwx SM=PRV  
VM_ALLOCATE               14000800000-14004000000  [ 3584        0        0        0   ] ---/rwx SM=NUL

这与 tracealloc 所说的一致:

tracealloc(0x14000180000, 0x3d2000, uint8)

然后

tracefree(0x14000180000, 0x3d2000)

这个大字节切片跨越了 14000000000-14000400000 和 14000400000-14000800000 区域。然而,释放似乎只将 14000400000-14000800000 区域内的页面标记为可回收。(84 页 = 1344KB,这正是 footprint 报告的可回收空间。)在 14000000000-14000400000 区域内的页面仍然被标记为脏。
作为实验,我将 sysUnused 更改为也调用 mprotect(v, n, _PROT_NONE) 然后 mprotect(v, n, _PROT_READ|_PROT_WRITE)。参见 tailscale@38ab03e
使用此更改再次运行,footprint 报告的不可回收空间消失了。在三个暂停点:

Dirty      Clean  Reclaimable    Regions    Category
    ---        ---          ---        ---    ---
1168 KB        0 B          0 B         37    untagged ("VM_ALLOCATE")
Dirty      Clean  Reclaimable    Regions    Category
    ---        ---          ---        ---    ---
5328 KB        0 B          0 B         38    untagged ("VM_ALLOCATE")
Dirty      Clean  Reclaimable    Regions    Category
    ---        ---          ---        ---    ---
1584 KB        0 B          0 B         39    untagged ("VM_ALLOCATE")

我们没有回到 1168KB(我希望我知道为什么),但它比 2848KB 要好得多。 vmmap 显示与以前大致相同的模式:

REGION TYPE                    START - END         [   VSIZE    RSDNT    DIRTY     SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
VM_ALLOCATE               14000000000-14000400000  [  256       40       40        0   ] rw-/rwx SM=ZER  
VM_ALLOCATE               14000400000-14004000000  [ 3840        0        0        0   ] ---/rwx SM=NUL
REGION TYPE                    START - END         [   VSIZE    RSDNT    DIRTY     SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
VM_ALLOCATE               14000000000-14000400000  [  256      188      188       12   ] rw-/rwx SM=ZER  
VM_ALLOCATE               14000400000-14000800000  [  256       85       85        0   ] rw-/rwx SM=PRV  
VM_ALLOCATE               14000800000-14004000000  [ 3584        0        0        0   ] ---/rwx SM=NUL
REGION TYPE                    START - END         [   VSIZE    RSDNT    DIRTY     SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
VM_ALLOCATE               14000000000-14000400000  [  256      197      197        5   ] rw-/rwx SM=ZER  
VM_ALLOCATE               14000400000-14000800000  [  256       85        1        0   ] rw-/rwx SM=PRV  
VM_ALLOCATE               14000800000-14004000000  [ 3584        0        0        0   ] ---/rwx SM=NUL

(注意,如果你在 mprotect 运行中将脏页和交换页加在一起,它们与 xm

camsedfj

camsedfj1#

我正在研究iOS问题,但不知何故从未注意到这一点。对于这个几年后的回复,我深感抱歉。

我认为可能的情况是,MADV_FREE_REUSABLE仍然表现得像MADV_FREE,即操作系统实际上不会收回内存,除非有内存压力。我希望我知道如何进一步调查这个问题。MADV_FREE_REUSABLE实际上并没有被记录下来,而且据我所知,每个人都在猜测它的作用,或者询问在苹果工作的某人。据我所知,这是系统中唯一一个实际更新系统指标的madvise

在我的经验中,它也相当慢(特别是在MADV_FREE_REUSE这边,我记得)。我在想我们是否应该为所有未来的darwin版本执行“重新Map为读/写”。我会对差异进行基准测试。如果没有差异,我会切换到重新Map的那个。

相关问题