aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/mcache.go
diff options
context:
space:
mode:
authorMichael Anthony Knyszek <mknyszek@google.com>2020-08-04 17:29:03 +0000
committerMichael Knyszek <mknyszek@google.com>2020-10-26 18:28:56 +0000
commit79781e8dd382ac34e502ed6a088dff6860a08c05 (patch)
tree37ad5dea76bd2dabfb360c05e6e9f040220a9739 /src/runtime/mcache.go
parentf77a9025f1e4bf4bb3e2b582d13cce5f19c1ca51 (diff)
downloadgo-79781e8dd382ac34e502ed6a088dff6860a08c05.tar.gz
go-79781e8dd382ac34e502ed6a088dff6860a08c05.zip
runtime: move malloc stats into consistentHeapStats
This change moves the mcache-local malloc stats into the consistentHeapStats structure so the malloc stats can be managed consistently with the memory stats. The one exception here is tinyAllocs for which moving that into the global stats would incur several atomic writes on the fast path. Microbenchmarks for just one CPU core have shown a 50% loss in throughput. Since tiny allocation counnt isn't exposed anyway and is always blindly added to both allocs and frees, let that stay inconsistent and flush the tiny allocation count every so often. Change-Id: I2a4b75f209c0e659b9c0db081a3287bf227c10ca Reviewed-on: https://go-review.googlesource.com/c/go/+/247039 Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Go Bot <gobot@golang.org> Trust: Michael Knyszek <mknyszek@google.com> Reviewed-by: Michael Pratt <mpratt@google.com>
Diffstat (limited to 'src/runtime/mcache.go')
-rw-r--r--src/runtime/mcache.go70
1 files changed, 23 insertions, 47 deletions
diff --git a/src/runtime/mcache.go b/src/runtime/mcache.go
index e27a1c9ec0..c9342a41c9 100644
--- a/src/runtime/mcache.go
+++ b/src/runtime/mcache.go
@@ -32,8 +32,12 @@ type mcache struct {
// tiny is a heap pointer. Since mcache is in non-GC'd memory,
// we handle it by clearing it in releaseAll during mark
// termination.
+ //
+ // tinyAllocs is the number of tiny allocations performed
+ // by the P that owns this mcache.
tiny uintptr
tinyoffset uintptr
+ tinyAllocs uintptr
// The rest is not accessed on every malloc.
@@ -41,21 +45,6 @@ type mcache struct {
stackcache [_NumStackOrders]stackfreelist
- // Allocator stats (source-of-truth).
- // Only the P that owns this mcache may write to these
- // variables, so it's safe for that P to read non-atomically.
- //
- // When read with stats from other mcaches and with the world
- // stopped, the result will accurately reflect the state of the
- // application.
- tinyAllocCount uintptr // number of tiny allocs not counted in other stats
- largeAlloc uintptr // bytes allocated for large objects
- largeAllocCount uintptr // number of large object allocations
- smallAllocCount [_NumSizeClasses]uintptr // number of allocs for small objects
- largeFree uintptr // bytes freed for large objects (>maxSmallSize)
- largeFreeCount uintptr // number of frees for large objects (>maxSmallSize)
- smallFreeCount [_NumSizeClasses]uintptr // number of frees for small objects (<=maxSmallSize)
-
// flushGen indicates the sweepgen during which this mcache
// was last flushed. If flushGen != mheap_.sweepgen, the spans
// in this mcache are stale and need to the flushed so they
@@ -117,7 +106,7 @@ func allocmcache() *mcache {
// In some cases there is no way to simply release
// resources, such as statistics, so donate them to
// a different mcache (the recipient).
-func freemcache(c *mcache, recipient *mcache) {
+func freemcache(c *mcache) {
systemstack(func() {
c.releaseAll()
stackcache_clear(c)
@@ -128,8 +117,6 @@ func freemcache(c *mcache, recipient *mcache) {
// gcworkbuffree(c.gcworkbuf)
lock(&mheap_.lock)
- // Donate anything else that's left.
- c.donate(recipient)
mheap_.cachealloc.free(unsafe.Pointer(c))
unlock(&mheap_.lock)
})
@@ -158,31 +145,6 @@ func getMCache() *mcache {
return c
}
-// donate flushes data and resources which have no global
-// pool to another mcache.
-func (c *mcache) donate(d *mcache) {
- // scanAlloc is handled separately because it's not
- // like these stats -- it's used for GC pacing.
- d.largeAlloc += c.largeAlloc
- c.largeAlloc = 0
- d.largeAllocCount += c.largeAllocCount
- c.largeAllocCount = 0
- for i := range c.smallAllocCount {
- d.smallAllocCount[i] += c.smallAllocCount[i]
- c.smallAllocCount[i] = 0
- }
- d.largeFree += c.largeFree
- c.largeFree = 0
- d.largeFreeCount += c.largeFreeCount
- c.largeFreeCount = 0
- for i := range c.smallFreeCount {
- d.smallFreeCount[i] += c.smallFreeCount[i]
- c.smallFreeCount[i] = 0
- }
- d.tinyAllocCount += c.tinyAllocCount
- c.tinyAllocCount = 0
-}
-
// refill acquires a new span of span class spc for c. This span will
// have at least one free object. The current span in c must be full.
//
@@ -219,12 +181,20 @@ func (c *mcache) refill(spc spanClass) {
// Assume all objects from this span will be allocated in the
// mcache. If it gets uncached, we'll adjust this.
- c.smallAllocCount[spc.sizeclass()] += uintptr(s.nelems) - uintptr(s.allocCount)
+ stats := memstats.heapStats.acquire(c)
+ atomic.Xadduintptr(&stats.smallAllocCount[spc.sizeclass()], uintptr(s.nelems)-uintptr(s.allocCount))
+ memstats.heapStats.release(c)
// Update heap_live with the same assumption.
usedBytes := uintptr(s.allocCount) * s.elemsize
atomic.Xadd64(&memstats.heap_live, int64(s.npages*pageSize)-int64(usedBytes))
+ // Flush tinyAllocs.
+ if spc == tinySpanClass {
+ atomic.Xadd64(&memstats.tinyallocs, int64(c.tinyAllocs))
+ c.tinyAllocs = 0
+ }
+
// While we're here, flush scanAlloc, since we have to call
// revise anyway.
atomic.Xadd64(&memstats.heap_scan, int64(c.scanAlloc))
@@ -262,8 +232,10 @@ func (c *mcache) allocLarge(size uintptr, needzero bool, noscan bool) *mspan {
if s == nil {
throw("out of memory")
}
- c.largeAlloc += npages * pageSize
- c.largeAllocCount++
+ stats := memstats.heapStats.acquire(c)
+ atomic.Xadduintptr(&stats.largeAlloc, npages*pageSize)
+ atomic.Xadduintptr(&stats.largeAllocCount, 1)
+ memstats.heapStats.release(c)
// Update heap_live and revise pacing if needed.
atomic.Xadd64(&memstats.heap_live, int64(npages*pageSize))
@@ -294,7 +266,9 @@ func (c *mcache) releaseAll() {
if s != &emptymspan {
// Adjust nsmallalloc in case the span wasn't fully allocated.
n := uintptr(s.nelems) - uintptr(s.allocCount)
- c.smallAllocCount[spanClass(i).sizeclass()] -= n
+ stats := memstats.heapStats.acquire(c)
+ atomic.Xadduintptr(&stats.smallAllocCount[spanClass(i).sizeclass()], -n)
+ memstats.heapStats.release(c)
if s.sweepgen != sg+1 {
// refill conservatively counted unallocated slots in heap_live.
// Undo this.
@@ -313,6 +287,8 @@ func (c *mcache) releaseAll() {
// Clear tinyalloc pool.
c.tiny = 0
c.tinyoffset = 0
+ atomic.Xadd64(&memstats.tinyallocs, int64(c.tinyAllocs))
+ c.tinyAllocs = 0
// Updated heap_scan and possible heap_live.
if gcBlackenEnabled != 0 {