这实际上是对 #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
1条答案
按热度按时间camsedfj1#
我正在研究iOS问题,但不知何故从未注意到这一点。对于这个几年后的回复,我深感抱歉。
我认为可能的情况是,
MADV_FREE_REUSABLE
仍然表现得像MADV_FREE
,即操作系统实际上不会收回内存,除非有内存压力。我希望我知道如何进一步调查这个问题。MADV_FREE_REUSABLE
实际上并没有被记录下来,而且据我所知,每个人都在猜测它的作用,或者询问在苹果工作的某人。据我所知,这是系统中唯一一个实际更新系统指标的madvise
。在我的经验中,它也相当慢(特别是在
MADV_FREE_REUSE
这边,我记得)。我在想我们是否应该为所有未来的darwin版本执行“重新Map为读/写”。我会对差异进行基准测试。如果没有差异,我会切换到重新Map的那个。