aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Ripley <nick.ripley@datadoghq.com>2026-03-27 09:35:51 -0400
committerNick Ripley <nick.ripley@datadoghq.com>2026-03-28 13:07:33 -0700
commitd247ed00e498e9717fb7c80d126bee5a8afdb4e8 (patch)
tree07641e62bb021d5cd34a0ad0a2fc9ea30fbb9c47
parent1fd68799c39bd4a3f7e16a1ee24fcaca3efe5357 (diff)
downloadgo-d247ed00e498e9717fb7c80d126bee5a8afdb4e8.tar.xz
runtime: remove redundant fields from memory profile records
The memProfCycle struct holds allocation counts and bytes allocated, and frees and bytes freed. But the memory profile records are already aggregated by allocation size, which is stored in the "size" field of the bucket struct. We can derive the bytes allocated/freed using the counts and the size we already store. Thus we can delete the bytes fields from memProfCycle, saving 64 bytes per memRecord. We can do something similar for the profilerecord.MemProfileRecord type. We just need to know the object size and we can derive the allocated and freed bytes accordingly. Change-Id: I103885c2f29471b25283e330674fc16d6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/760140 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Michael Pratt <mpratt@google.com>
-rw-r--r--src/internal/profilerecord/profilerecord.go4
-rw-r--r--src/runtime/mheap.go2
-rw-r--r--src/runtime/mprof.go22
-rw-r--r--src/runtime/pprof/pprof.go6
-rw-r--r--src/runtime/pprof/protomem.go23
-rw-r--r--src/runtime/pprof/protomem_test.go6
6 files changed, 25 insertions, 38 deletions
diff --git a/src/internal/profilerecord/profilerecord.go b/src/internal/profilerecord/profilerecord.go
index a5efdced8f..95581867e1 100644
--- a/src/internal/profilerecord/profilerecord.go
+++ b/src/internal/profilerecord/profilerecord.go
@@ -13,12 +13,12 @@ type StackRecord struct {
}
type MemProfileRecord struct {
- AllocBytes, FreeBytes int64
+ ObjectSize int64
AllocObjects, FreeObjects int64
Stack []uintptr
}
-func (r *MemProfileRecord) InUseBytes() int64 { return r.AllocBytes - r.FreeBytes }
+func (r *MemProfileRecord) InUseBytes() int64 { return r.InUseObjects() * r.ObjectSize }
func (r *MemProfileRecord) InUseObjects() int64 { return r.AllocObjects - r.FreeObjects }
type BlockProfileRecord struct {
diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go
index a7b361cff7..b85e111128 100644
--- a/src/runtime/mheap.go
+++ b/src/runtime/mheap.go
@@ -2790,7 +2790,7 @@ func freeSpecial(s *special, p unsafe.Pointer, size uintptr) {
unlock(&mheap_.speciallock)
case _KindSpecialProfile:
sp := (*specialprofile)(unsafe.Pointer(s))
- mProf_Free(sp.b, size)
+ mProf_Free(sp.b)
lock(&mheap_.speciallock)
mheap_.specialprofilealloc.free(unsafe.Pointer(sp))
unlock(&mheap_.speciallock)
diff --git a/src/runtime/mprof.go b/src/runtime/mprof.go
index a4cfef72fa..9ab02fbb30 100644
--- a/src/runtime/mprof.go
+++ b/src/runtime/mprof.go
@@ -146,16 +146,13 @@ type memRecord struct {
// memRecordCycle
type memRecordCycle struct {
- allocs, frees uintptr
- alloc_bytes, free_bytes uintptr
+ allocs, frees uintptr
}
// add accumulates b into a. It does not zero b.
func (a *memRecordCycle) add(b *memRecordCycle) {
a.allocs += b.allocs
a.frees += b.frees
- a.alloc_bytes += b.alloc_bytes
- a.free_bytes += b.free_bytes
}
// A blockRecord is the bucket data for a bucket of type blockProfile,
@@ -453,7 +450,6 @@ func mProf_Malloc(mp *m, p unsafe.Pointer, size uintptr) {
lock(&profMemFutureLock[index])
mpc.allocs++
- mpc.alloc_bytes += size
unlock(&profMemFutureLock[index])
// Setprofilebucket locks a bunch of other mutexes, so we call it outside of
@@ -466,7 +462,7 @@ func mProf_Malloc(mp *m, p unsafe.Pointer, size uintptr) {
}
// Called when freeing a profiled block.
-func mProf_Free(b *bucket, size uintptr) {
+func mProf_Free(b *bucket) {
index := (mProfCycle.read() + 1) % uint32(len(memRecord{}.future))
mp := b.mp()
@@ -474,7 +470,6 @@ func mProf_Free(b *bucket, size uintptr) {
lock(&profMemFutureLock[index])
mpc.frees++
- mpc.free_bytes += size
unlock(&profMemFutureLock[index])
}
@@ -960,7 +955,7 @@ func memProfileInternal(size int, inuseZero bool, copyFn func(profilerecord.MemP
head := (*bucket)(mbuckets.Load())
for b := head; b != nil; b = b.allnext {
mp := b.mp()
- if inuseZero || mp.active.alloc_bytes != mp.active.free_bytes {
+ if inuseZero || mp.active.allocs != mp.active.frees {
n++
}
if mp.active.allocs != 0 || mp.active.frees != 0 {
@@ -981,7 +976,7 @@ func memProfileInternal(size int, inuseZero bool, copyFn func(profilerecord.MemP
mp.future[c] = memRecordCycle{}
unlock(&profMemFutureLock[c])
}
- if inuseZero || mp.active.alloc_bytes != mp.active.free_bytes {
+ if inuseZero || mp.active.allocs != mp.active.frees {
n++
}
}
@@ -990,10 +985,9 @@ func memProfileInternal(size int, inuseZero bool, copyFn func(profilerecord.MemP
ok = true
for b := head; b != nil; b = b.allnext {
mp := b.mp()
- if inuseZero || mp.active.alloc_bytes != mp.active.free_bytes {
+ if inuseZero || mp.active.allocs != mp.active.frees {
r := profilerecord.MemProfileRecord{
- AllocBytes: int64(mp.active.alloc_bytes),
- FreeBytes: int64(mp.active.free_bytes),
+ ObjectSize: int64(b.size),
AllocObjects: int64(mp.active.allocs),
FreeObjects: int64(mp.active.frees),
Stack: b.stk(),
@@ -1007,8 +1001,8 @@ func memProfileInternal(size int, inuseZero bool, copyFn func(profilerecord.MemP
}
func copyMemProfileRecord(dst *MemProfileRecord, src profilerecord.MemProfileRecord) {
- dst.AllocBytes = src.AllocBytes
- dst.FreeBytes = src.FreeBytes
+ dst.AllocBytes = src.AllocObjects * src.ObjectSize
+ dst.FreeBytes = src.FreeObjects * src.ObjectSize
dst.AllocObjects = src.AllocObjects
dst.FreeObjects = src.FreeObjects
if raceenabled {
diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go
index d560eeade1..6708d2dfa3 100644
--- a/src/runtime/pprof/pprof.go
+++ b/src/runtime/pprof/pprof.go
@@ -674,9 +674,9 @@ func writeHeapInternal(w io.Writer, debug int, defaultSampleType string) error {
var total runtime.MemProfileRecord
for i := range p {
r := &p[i]
- total.AllocBytes += r.AllocBytes
+ total.AllocBytes += r.AllocObjects * r.ObjectSize
total.AllocObjects += r.AllocObjects
- total.FreeBytes += r.FreeBytes
+ total.FreeBytes += r.FreeObjects * r.ObjectSize
total.FreeObjects += r.FreeObjects
}
@@ -706,7 +706,7 @@ func writeHeapInternal(w io.Writer, debug int, defaultSampleType string) error {
r := &p[i]
fmt.Fprintf(w, "%d: %d [%d: %d] @",
r.InUseObjects(), r.InUseBytes(),
- r.AllocObjects, r.AllocBytes)
+ r.AllocObjects, r.AllocObjects*r.ObjectSize)
for _, pc := range r.Stack {
fmt.Fprintf(w, " %#x", pc)
}
diff --git a/src/runtime/pprof/protomem.go b/src/runtime/pprof/protomem.go
index e0d3746e36..02847c2dda 100644
--- a/src/runtime/pprof/protomem.go
+++ b/src/runtime/pprof/protomem.go
@@ -51,16 +51,10 @@ func writeHeapProto(w io.Writer, p []profilerecord.MemProfileRecord, rate int64,
hideRuntime = false // try again, and show all frames next time.
}
- values[0], values[1] = scaleHeapSample(r.AllocObjects, r.AllocBytes, rate)
- values[2], values[3] = scaleHeapSample(r.InUseObjects(), r.InUseBytes(), rate)
- var blockSize int64
- if r.AllocObjects > 0 {
- blockSize = r.AllocBytes / r.AllocObjects
- }
+ values[0], values[1] = scaleHeapSample(r.AllocObjects, r.ObjectSize, rate)
+ values[2], values[3] = scaleHeapSample(r.InUseObjects(), r.ObjectSize, rate)
b.pbSample(values, locs, func() {
- if blockSize != 0 {
- b.pbLabel(tagSample_Label, "bytes", "", blockSize)
- }
+ b.pbLabel(tagSample_Label, "bytes", "", r.ObjectSize)
})
}
return b.build()
@@ -75,19 +69,18 @@ func writeHeapProto(w io.Writer, p []profilerecord.MemProfileRecord, rate int64,
// which samples to collect, based on the desired average collection
// rate R. The probability of a sample of size S to appear in that
// profile is 1-exp(-S/R).
-func scaleHeapSample(count, size, rate int64) (int64, int64) {
- if count == 0 || size == 0 {
+func scaleHeapSample(count, avgSize, rate int64) (int64, int64) {
+ if count == 0 || avgSize == 0 {
return 0, 0
}
if rate <= 1 {
// if rate==1 all samples were collected so no adjustment is needed.
// if rate<1 treat as unknown and skip scaling.
- return count, size
+ return count, count * avgSize
}
- avgSize := float64(size) / float64(count)
- scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
+ scale := 1 / (1 - math.Exp(-float64(avgSize)/float64(rate)))
- return int64(float64(count) * scale), int64(float64(size) * scale)
+ return int64(float64(count) * scale), int64(float64(count*avgSize) * scale)
}
diff --git a/src/runtime/pprof/protomem_test.go b/src/runtime/pprof/protomem_test.go
index 6f3231d42a..6b2254298e 100644
--- a/src/runtime/pprof/protomem_test.go
+++ b/src/runtime/pprof/protomem_test.go
@@ -27,9 +27,9 @@ func TestConvertMemProfile(t *testing.T) {
a1, a2 := uintptr(addr1)+1, uintptr(addr2)+1
rate := int64(512 * 1024)
rec := []profilerecord.MemProfileRecord{
- {AllocBytes: 4096, FreeBytes: 1024, AllocObjects: 4, FreeObjects: 1, Stack: []uintptr{a1, a2}},
- {AllocBytes: 512 * 1024, FreeBytes: 0, AllocObjects: 1, FreeObjects: 0, Stack: []uintptr{a2 + 1, a2 + 2}},
- {AllocBytes: 512 * 1024, FreeBytes: 512 * 1024, AllocObjects: 1, FreeObjects: 1, Stack: []uintptr{a1 + 1, a1 + 2, a2 + 3}},
+ {ObjectSize: 1024, AllocObjects: 4, FreeObjects: 1, Stack: []uintptr{a1, a2}},
+ {ObjectSize: 512 * 1024, AllocObjects: 1, FreeObjects: 0, Stack: []uintptr{a2 + 1, a2 + 2}},
+ {ObjectSize: 512 * 1024, AllocObjects: 1, FreeObjects: 1, Stack: []uintptr{a1 + 1, a1 + 2, a2 + 3}},
}
periodType := &profile.ValueType{Type: "space", Unit: "bytes"}