如何在Golang中设置进程的内存限制

dxxyhpgq  于 2022-09-21  发布在  Go
关注(0)|答案(2)|浏览(803)

我使用syscall prlimit来设置要处理的资源限制,它适用于限制CPU时间,但在测试内存使用时,我遇到了这个问题。

package sandbox

import (
    "syscall"
    "unsafe"
)

func prLimit(pid int, limit uintptr, rlimit *syscall.Rlimit) error {
    _, _, errno := syscall.RawSyscall6(syscall.SYS_PRLIMIT64, uintptr(pid), limit, uintptr(unsafe.Pointer(rlimit)), 0, 0, 0)
    var err error
    if errno != 0 {
        err = errno
        return err
    } else {
        return nil
    }
}

这是我的测试。

func TestMemoryLimit(t *testing.T) {
    proc, err := os.StartProcess("test/memo", []string{"memo"}, &os.ProcAttr{})
    if err != nil {
        panic(err)
    }
    defer proc.Kill()
    var rlimit syscall.Rlimit
    rlimit.Cur = 10
    rlimit.Max = 10 + 1024
    prLimit(proc.Pid, syscall.RLIMIT_DATA, &rlimit)
    status, err := proc.Wait()
    if status.Success() {
        t.Fatal("memory test failed")
    }
}

这是备忘录:

package main

func main() {
    var a [10000][]int
    for i := 0; i < 1000; i++ {
        a[i] = make([]int, 1024)
    }
}

我制造了大量内存,并且只为内存设置了10个字节,但它无论如何都不会发出段故障信号。

jum4pzuy

jum4pzuy1#

RLIMIT_DATA描述进程数据段的最大大小。传统上,分配内存的程序通过调用brk()从操作系统分配内存来扩大数据段。

围棋不使用这种方法。相反,它使用mmap()系统调用的变体来请求地址空间中任何位置的内存区域。这比基于brk()的方法灵活得多,因为您可以使用munmap()释放任意内存区域,而基于brk()的方法只能从数据段的末尾释放内存。

其结果是RLIMIT_DATA在控制进程使用的内存量方面无效。尝试使用RLIMIT_AS,但请注意,此限制还包含用于文件Map的地址空间,尤其是在共享库的情况下。

hjzp0vay

hjzp0vay2#

有一个提案Soft memory limit,可能会在GO 1.18之后发布
该选项有两种形式:名为SetMemoryLimit的新runtime/debug函数和GOMEMLIMIT环境变量。总而言之,运行库将通过限制堆的大小以及更积极地将内存返回给底层平台来尝试保持此内存限制。这包括使用一种机制来帮助缓解垃圾收集死亡螺旋。最后,通过设置GOGC=OFF,Go运行时将始终将堆增长到全部内存限制。

这一新选项使应用程序能够更好地控制其资源经济性。它使用户能够:

  • 更好地利用他们已经拥有的内存,
  • 自信地降低他们的内存限制,知道围棋会尊重他们,
  • 避免不受支持的垃圾收集调优形式。

更新

此功能将于Go 1.19发布
运行时现在包括对软内存限制的支持。此内存限制包括Go堆和运行时管理的所有其他内存,并且不包括外部内存源,如二进制文件本身的Map、以其他语言管理的内存以及由操作系统代表Go程序持有的内存。

此限制可通过runtime/debug.SetMemoryLimit或等效的GOMEMLIMIT环境变量进行管理。

该限制与runtime/debug.SetGCPercent/GOGC一起使用,即使是GOGC=off也会受到尊重,从而允许围棋程序始终最大限度地利用其内存限制,从而在某些情况下提高资源效率。

以下是有关每个A Guide to the Go Garbage Collectormemory limit用法的一些建议

虽然内存限制是一个强大的工具,Go运行时会采取措施减少误用带来的最糟糕的行为,但仔细使用它仍然很重要。以下是关于内存限制在哪里最有用和适用,以及在哪里可能弊大于利的一些建议。

当您的Go程序的执行环境完全在您的控制范围之内,并且Go程序是唯一可以访问某一组资源(即某种内存预留,如容器内存限制)的程序时,*确实**利用内存限制。

一个很好的例子是将Web服务部署到具有固定可用内存量的容器中。

在这种情况下,一个很好的经验法则是留出额外的5%-10%的净空来考虑Go运行时不知道的内存源。

  • 确保**随时调整内存限制,以适应不断变化的条件。

一个很好的例子是CGO程序,在该程序中,C库临时需要使用相当多的内存。
如果Go程序可能与其他程序共享其有限内存的一部分,并且这些程序通常与Go程序解耦,则*不要**将GOGC设置为OFF并设置内存限制。相反,保留内存限制,因为它可能有助于抑制不受欢迎的瞬时行为,但对于平均情况,请将GOGC设置为某个较小的合理值。

虽然尝试为联合租户程序“预留”内存可能很诱人,但除非这些程序是完全同步的(例如,Go程序在其被调用者执行时调用一些子进程和块),否则结果将不太可靠,因为两个程序都不可避免地需要更多内存。让围棋程序在不需要的时候使用更少的内存,总体上会产生更可靠的结果。此建议也适用于过度提交的情况,即在一台计算机上运行的容器的内存限制总和可能超过该计算机可用的实际物理内存。

  • 当部署到您不能控制的执行环境时,尤其是当您的程序的内存使用与其输入成正比时,不要**使用内存限制。

CLI工具或桌面应用程序就是一个很好的例子。在程序中设置内存限制可能会导致令人困惑的崩溃和糟糕的性能,因为不清楚可能会输入什么类型的输入,或者系统上可能有多少内存可用。此外,高级最终用户可以随时设置内存限制(如果他们愿意)。

  • 不要**设置内存限制,以避免在程序已经接近其环境的内存限制时出现内存不足的情况。

这有效地将内存不足的风险替换为严重的应用程序减速风险,这通常不是一种有利的交易,即使围棋做出了减轻打击的努力。在这种情况下,要么增加环境的内存限制(然后可能设置内存限制),要么减少GOGC(它提供了比抖动缓解更干净的权衡),这将更加有效。

相关问题