diff options
author | Michael Anthony Knyszek <mknyszek@google.com> | 2019-11-15 23:30:30 +0000 |
---|---|---|
committer | Michael Knyszek <mknyszek@google.com> | 2019-12-11 19:37:19 +0000 |
commit | 9d78e75a0a55fd5ff3d68b4cba2f0395c4b5dc88 (patch) | |
tree | ce3494c184835c66c8c280b15d2b2ce9b6a0a7ae /src/runtime/mpagealloc_test.go | |
parent | ef3ef8fcdfcd5e8a70b4a8feb2f91a82fee1f603 (diff) | |
download | go-9d78e75a0a55fd5ff3d68b4cba2f0395c4b5dc88.tar.gz go-9d78e75a0a55fd5ff3d68b4cba2f0395c4b5dc88.zip |
runtime: track ranges of address space which are owned by the heap
This change adds a new inUse field to the allocator which tracks ranges
of addresses that are owned by the heap. It is updated on each heap
growth.
These ranges are tracked in an array which is kept sorted. In practice
this array shouldn't exceed its initial allocation except in rare cases
and thus should be small (ideally exactly 1 element in size).
In a hypothetical worst-case scenario wherein we have a 1 TiB heap and 4
MiB arenas (note that the address ranges will never be at a smaller
granularity than an arena, since arenas are always allocated
contiguously), inUse would use at most 4 MiB of memory if the heap
mappings were completely discontiguous (highly unlikely) with an
additional 2 MiB leaked from previous allocations. Furthermore, the
copies that are done to keep the inUse array sorted will copy at most 4
MiB of memory in such a scenario, which, assuming a conservative copying
rate of 5 GiB/s, amounts to about 800µs.
However, note that in practice:
1) Most 64-bit platforms have 64 MiB arenas.
2) The copies should incur little-to-no page faults, meaning a copy rate
closer to 25-50 GiB/s is expected.
3) Go heaps are almost always mostly contiguous.
Updates #35514.
Change-Id: I3ad07f1c2b5b9340acf59ecc3b9ae09e884814fe
Reviewed-on: https://go-review.googlesource.com/c/go/+/207757
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Diffstat (limited to 'src/runtime/mpagealloc_test.go')
-rw-r--r-- | src/runtime/mpagealloc_test.go | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/src/runtime/mpagealloc_test.go b/src/runtime/mpagealloc_test.go index 2da1117592..e09dae00a1 100644 --- a/src/runtime/mpagealloc_test.go +++ b/src/runtime/mpagealloc_test.go @@ -40,6 +40,119 @@ func checkPageAlloc(t *testing.T, want, got *PageAlloc) { // TODO(mknyszek): Verify summaries too? } +func TestPageAllocGrow(t *testing.T) { + tests := map[string]struct { + chunks []ChunkIdx + inUse []AddrRange + }{ + "One": { + chunks: []ChunkIdx{ + BaseChunkIdx, + }, + inUse: []AddrRange{ + {PageBase(BaseChunkIdx, 0), PageBase(BaseChunkIdx+1, 0)}, + }, + }, + "Contiguous2": { + chunks: []ChunkIdx{ + BaseChunkIdx, + BaseChunkIdx + 1, + }, + inUse: []AddrRange{ + {PageBase(BaseChunkIdx, 0), PageBase(BaseChunkIdx+2, 0)}, + }, + }, + "Contiguous5": { + chunks: []ChunkIdx{ + BaseChunkIdx, + BaseChunkIdx + 1, + BaseChunkIdx + 2, + BaseChunkIdx + 3, + BaseChunkIdx + 4, + }, + inUse: []AddrRange{ + {PageBase(BaseChunkIdx, 0), PageBase(BaseChunkIdx+5, 0)}, + }, + }, + "Discontiguous": { + chunks: []ChunkIdx{ + BaseChunkIdx, + BaseChunkIdx + 2, + BaseChunkIdx + 4, + }, + inUse: []AddrRange{ + {PageBase(BaseChunkIdx, 0), PageBase(BaseChunkIdx+1, 0)}, + {PageBase(BaseChunkIdx+2, 0), PageBase(BaseChunkIdx+3, 0)}, + {PageBase(BaseChunkIdx+4, 0), PageBase(BaseChunkIdx+5, 0)}, + }, + }, + "Mixed": { + chunks: []ChunkIdx{ + BaseChunkIdx, + BaseChunkIdx + 1, + BaseChunkIdx + 2, + BaseChunkIdx + 4, + }, + inUse: []AddrRange{ + {PageBase(BaseChunkIdx, 0), PageBase(BaseChunkIdx+3, 0)}, + {PageBase(BaseChunkIdx+4, 0), PageBase(BaseChunkIdx+5, 0)}, + }, + }, + "WildlyDiscontiguous": { + chunks: []ChunkIdx{ + BaseChunkIdx, + BaseChunkIdx + 1, + BaseChunkIdx + 0x10, + BaseChunkIdx + 0x21, + }, + inUse: []AddrRange{ + {PageBase(BaseChunkIdx, 0), PageBase(BaseChunkIdx+2, 0)}, + {PageBase(BaseChunkIdx+0x10, 0), PageBase(BaseChunkIdx+0x11, 0)}, + {PageBase(BaseChunkIdx+0x21, 0), PageBase(BaseChunkIdx+0x22, 0)}, + }, + }, + } + for name, v := range tests { + v := v + t.Run(name, func(t *testing.T) { + // By creating a new pageAlloc, we will + // grow it for each chunk defined in x. + x := make(map[ChunkIdx][]BitRange) + for _, c := range v.chunks { + x[c] = []BitRange{} + } + b := NewPageAlloc(x, nil) + defer FreePageAlloc(b) + + got := b.InUse() + want := v.inUse + + // Check for mismatches. + if len(got) != len(want) { + t.Fail() + } else { + for i := range want { + if want[i] != got[i] { + t.Fail() + break + } + } + } + if t.Failed() { + t.Logf("found inUse mismatch") + t.Logf("got:") + for i, r := range got { + t.Logf("\t#%d [0x%x, 0x%x)", i, r.Base, r.Limit) + } + t.Logf("want:") + for i, r := range want { + t.Logf("\t#%d [0x%x, 0x%x)", i, r.Base, r.Limit) + } + } + }) + } +} + func TestPageAllocAlloc(t *testing.T) { type hit struct { npages, base, scav uintptr |