Skip to content

Commit f67b2d8

Browse files
committed
runtime: use span.elemsize for accounting in mallocgc
Currently the final size computed for an object in mallocgc excludes the allocation header. This is correct in a number of cases, but definitely wrong for memory profiling because the "free" side accounts for the full allocation slot. This change makes an explicit distinction between the parts of mallocgc that care about the full allocation slot size ("the GC's accounting") and those that don't (pointer+len should always be valid). It then applies the appropriate size to the different forms of accounting in mallocgc. For #64153. Change-Id: I481b34b2bb9ff923b59e8408ab2b8fb9025ba944 Reviewed-on: https://go-review.googlesource.com/c/go/+/542735 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Michael Knyszek <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 0b31a46 commit f67b2d8

File tree

3 files changed

+29
-14
lines changed

3 files changed

+29
-14
lines changed

src/runtime/arena.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ func newUserArenaChunk() (unsafe.Pointer, *mspan) {
589589
// This may be racing with GC so do it atomically if there can be
590590
// a race marking the bit.
591591
if gcphase != _GCoff {
592-
gcmarknewobject(span, span.base(), span.elemsize)
592+
gcmarknewobject(span, span.base())
593593
}
594594

595595
if raceenabled {

src/runtime/malloc.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,12 +1221,7 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
12211221
// This may be racing with GC so do it atomically if there can be
12221222
// a race marking the bit.
12231223
if gcphase != _GCoff {
1224-
// Pass the full size of the allocation to the number of bytes
1225-
// marked.
1226-
//
1227-
// If !goexperiment.AllocHeaders, "size" doesn't include the
1228-
// allocation header, so use span.elemsize unconditionally.
1229-
gcmarknewobject(span, uintptr(x), span.elemsize)
1224+
gcmarknewobject(span, uintptr(x))
12301225
}
12311226

12321227
if raceenabled {
@@ -1248,12 +1243,28 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
12481243
asanunpoison(x, userSize)
12491244
}
12501245

1246+
// If !goexperiment.AllocHeaders, "size" doesn't include the
1247+
// allocation header, so use span.elemsize as the "full" size
1248+
// for various computations below.
1249+
//
1250+
// TODO(mknyszek): We should really count the header as part
1251+
// of gc_sys or something, but it's risky to change the
1252+
// accounting so much right now. Just pretend its internal
1253+
// fragmentation and match the GC's accounting by using the
1254+
// whole allocation slot.
1255+
fullSize := size
1256+
if goexperiment.AllocHeaders {
1257+
fullSize = span.elemsize
1258+
}
12511259
if rate := MemProfileRate; rate > 0 {
12521260
// Note cache c only valid while m acquired; see #47302
1253-
if rate != 1 && size < c.nextSample {
1254-
c.nextSample -= size
1261+
//
1262+
// N.B. Use the full size because that matches how the GC
1263+
// will update the mem profile on the "free" side.
1264+
if rate != 1 && fullSize < c.nextSample {
1265+
c.nextSample -= fullSize
12551266
} else {
1256-
profilealloc(mp, x, size)
1267+
profilealloc(mp, x, fullSize)
12571268
}
12581269
}
12591270
mp.mallocing = 0
@@ -1268,6 +1279,7 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
12681279
if goexperiment.AllocHeaders && header != nil {
12691280
throw("unexpected malloc header in delayed zeroing of large object")
12701281
}
1282+
// N.B. size == fullSize always in this case.
12711283
memclrNoHeapPointersChunked(size, x) // This is a possible preemption point: see #47302
12721284
}
12731285

@@ -1278,14 +1290,17 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
12781290

12791291
if inittrace.active && inittrace.id == getg().goid {
12801292
// Init functions are executed sequentially in a single goroutine.
1281-
inittrace.bytes += uint64(size)
1293+
inittrace.bytes += uint64(fullSize)
12821294
}
12831295
}
12841296

12851297
if assistG != nil {
12861298
// Account for internal fragmentation in the assist
12871299
// debt now that we know it.
1288-
assistG.gcAssistBytes -= int64(size - dataSize)
1300+
//
1301+
// N.B. Use the full size because that's how the rest
1302+
// of the GC accounts for bytes marked.
1303+
assistG.gcAssistBytes -= int64(fullSize - dataSize)
12891304
}
12901305

12911306
if shouldhelpgc {

src/runtime/mgcmark.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,7 @@ func gcDumpObject(label string, obj, off uintptr) {
17181718
//
17191719
//go:nowritebarrier
17201720
//go:nosplit
1721-
func gcmarknewobject(span *mspan, obj, size uintptr) {
1721+
func gcmarknewobject(span *mspan, obj uintptr) {
17221722
if useCheckmark { // The world should be stopped so this should not happen.
17231723
throw("gcmarknewobject called while doing checkmark")
17241724
}
@@ -1734,7 +1734,7 @@ func gcmarknewobject(span *mspan, obj, size uintptr) {
17341734
}
17351735

17361736
gcw := &getg().m.p.ptr().gcw
1737-
gcw.bytesMarked += uint64(size)
1737+
gcw.bytesMarked += uint64(span.elemsize)
17381738
}
17391739

17401740
// gcMarkTinyAllocs greys all active tiny alloc blocks.

0 commit comments

Comments
 (0)