Skip to content

Commit 5572fc4

Browse files
committed
runtime: reserve 16GB of heap address space on 64-bit unix
allocateHeap capped the heap at 1GB for all targets, so with -gc=conservative or -gc=precise any single allocation approaching 1GB (for example a scrypt key-derivation buffer with N=1<<20, r=8) failed with out of memory regardless of available system RAM. Reserve 16GB of virtual address space on 64-bit targets instead. The mmap is a reservation, not a commitment: pages cost physical memory only when first touched, and the existing halve-on-failure loop still adapts when the map is refused. 32-bit targets keep the 1GB cap; the size is derived from TargetBits as a shifted constant, since a plain 16GB literal does not compile on 32-bit targets even in a dead branch. This is the direction the growHeap comment already points at: "If we run out of memory, we should consider increasing heapMaxSize on 64-bit systems." With this, the practical limit under the blocks GC on 64-bit hosts becomes actual system memory, matching the boehm default and big Go; true exhaustion surfaces as the OS's memory pressure handling rather than a fatal error at an arbitrary 1GB.
1 parent bdc4a21 commit 5572fc4

1 file changed

Lines changed: 12 additions & 1 deletion

File tree

src/runtime/runtime_unix.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,24 @@ func procUnpin() {
318318
var heapSize uintptr = 128 * 1024 // small amount to start
319319
var heapMaxSize uintptr
320320

321+
// heapMaxReserve is how much virtual address space to reserve for the heap:
322+
// 16GB (1<<34) on 64-bit targets and 1GB (1<<30) on 32-bit ones. The
323+
// reservation is not a commitment: pages cost physical RAM only when first
324+
// touched, and allocateHeap's halving loop still adapts if mmap refuses.
325+
// A flat 1GB cap on 64-bit made any single allocation approaching 1GB
326+
// (e.g. a scrypt key-derivation buffer with N=1<<20, r=8) fail regardless
327+
// of available system memory. Deriving the shift from TargetBits keeps the
328+
// constant within uintptr range on 32-bit targets, where a plain 16GB
329+
// literal would not compile even in a dead branch.
330+
const heapMaxReserve = 1 << (30 + 4*(TargetBits/64))
331+
321332
var heapStart, heapEnd uintptr
322333

323334
func allocateHeap() {
324335
// Allocate a large chunk of virtual memory. Because it is virtual, it won't
325336
// really be allocated in RAM. Memory will only be allocated when it is
326337
// first touched.
327-
heapMaxSize = 1 * 1024 * 1024 * 1024 // 1GB for the entire heap
338+
heapMaxSize = heapMaxReserve
328339
for {
329340
addr := mmap(nil, heapMaxSize, flag_PROT_READ|flag_PROT_WRITE, flag_MAP_PRIVATE|flag_MAP_ANONYMOUS, -1, 0)
330341
if addr == unsafe.Pointer(^uintptr(0)) {

0 commit comments

Comments
 (0)