2014-11-11 17:05:02 -05:00
|
|
|
// Copyright 2009 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package runtime
|
|
|
|
|
|
2018-08-23 13:14:19 -04:00
|
|
|
import (
|
2024-02-01 10:21:14 +08:00
|
|
|
"internal/runtime/atomic"
|
2025-03-04 19:02:48 +00:00
|
|
|
"internal/runtime/gc"
|
2024-07-23 11:43:23 -04:00
|
|
|
"internal/runtime/sys"
|
2018-08-23 13:14:19 -04:00
|
|
|
"unsafe"
|
|
|
|
|
)
|
2014-11-11 17:05:02 -05:00
|
|
|
|
2015-02-19 13:38:46 -05:00
|
|
|
// Per-thread (in Go, per-P) cache for small objects.
|
2020-07-23 21:10:29 +00:00
|
|
|
// This includes a small object cache and local allocation stats.
|
2015-02-19 13:38:46 -05:00
|
|
|
// No locking needed because it is per-thread (per-P).
|
runtime: clear tiny alloc cache in mark term, not sweep term
The tiny alloc cache is maintained in a pointer from non-GC'd memory
(mcache) to heap memory and hence must be handled carefully.
Currently we clear the tiny alloc cache during sweep termination and,
if it is assigned to a non-nil value during concurrent marking, we
depend on a write barrier to keep the new value alive. However, while
the compiler currently always generates this write barrier, we're
treading on thin ice because write barriers may not happen for writes
to non-heap memory (e.g., typedmemmove). Without this lucky write
barrier, the GC may free a current tiny block while it's still
reachable by the tiny allocator, leading to later memory corruption.
Change this code so that, rather than depending on the write barrier,
we simply clear the tiny cache during mark termination when we're
clearing all of the other mcaches. If the current tiny block is
reachable from regular pointers, it will be retained; if it isn't
reachable from regular pointers, it may be freed, but that's okay
because there won't be any pointers in non-GC'd memory to it.
Change-Id: I8230980d8612c35c2997b9705641a1f9f865f879
Reviewed-on: https://go-review.googlesource.com/16962
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2015-11-16 15:20:59 -05:00
|
|
|
//
|
|
|
|
|
// mcaches are allocated from non-GC'd memory, so any heap pointers
|
|
|
|
|
// must be specially handled.
|
2015-02-19 13:38:46 -05:00
|
|
|
type mcache struct {
|
2022-08-07 17:43:57 +07:00
|
|
|
_ sys.NotInHeap
|
|
|
|
|
|
2015-02-19 13:38:46 -05:00
|
|
|
// The following members are accessed on every malloc,
|
|
|
|
|
// so they are grouped here for better caching.
|
runtime: simplify mem profile checking in mallocgc
Checking whether the current allocation needs to be profiled is
currently branch-y and weirdly a lot of code. The branches are
generally predictable, but it's a surprising number of instructions.
Part of the problem is that MemProfileRate is just a global that can be
set at any time, so we need to load it and check certain settings
explicitly. In an ideal world, we would just always subtract from
nextSample and have a single branch to take the slow path if we
subtract below zero.
If MemProfileRate were a function, we could trash all the nextSample
values intentionally in each mcache. This would be slow, but
MemProfileRate changes rarely while the malloc hot path is well, hot.
Unfortunate...
Although this ideal world is, AFAICT, impossible, we can still get
close. If we cache the value of MemProfileRate in each mcache, then we
can force malloc to take the slow path whenever MemProfileRate changes.
This does require two additional loads, but crucially, these loads are
independent of everything else in mallocgc. Furthermore, the branch
dependent on those loads is incredibly predictable in practice.
This CL on its own has little-to-no impact on mallocgc. But this
codepath is going to be duplicated in several places in the next CL, so
it'll pay to simplify it. Also, we're very much trying to remedy a
death-by-a-thousand-cuts situation, and malloc is currently still kind
of a monster -- it will not help if mallocgc isn't really streamlined
itself.
Lastly, there's a nice property now that all nextSample values get
immediately re-sampled when MemProfileRate changes.
Change-Id: I6443d0cf9bd7861595584442b675ac1be8ea3455
Reviewed-on: https://go-review.googlesource.com/c/go/+/615815
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
2024-09-25 16:51:52 +00:00
|
|
|
nextSample int64 // trigger heap sample after allocating this many bytes
|
|
|
|
|
memProfRate int // cached mem profile rate, used to detect changes
|
|
|
|
|
scanAlloc uintptr // bytes of scannable heap allocated
|
runtime: clear tiny alloc cache in mark term, not sweep term
The tiny alloc cache is maintained in a pointer from non-GC'd memory
(mcache) to heap memory and hence must be handled carefully.
Currently we clear the tiny alloc cache during sweep termination and,
if it is assigned to a non-nil value during concurrent marking, we
depend on a write barrier to keep the new value alive. However, while
the compiler currently always generates this write barrier, we're
treading on thin ice because write barriers may not happen for writes
to non-heap memory (e.g., typedmemmove). Without this lucky write
barrier, the GC may free a current tiny block while it's still
reachable by the tiny allocator, leading to later memory corruption.
Change this code so that, rather than depending on the write barrier,
we simply clear the tiny cache during mark termination when we're
clearing all of the other mcaches. If the current tiny block is
reachable from regular pointers, it will be retained; if it isn't
reachable from regular pointers, it may be freed, but that's okay
because there won't be any pointers in non-GC'd memory to it.
Change-Id: I8230980d8612c35c2997b9705641a1f9f865f879
Reviewed-on: https://go-review.googlesource.com/16962
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2015-11-16 15:20:59 -05:00
|
|
|
|
2015-02-19 13:38:46 -05:00
|
|
|
// Allocator cache for tiny objects w/o pointers.
|
|
|
|
|
// See "Tiny allocator" comment in malloc.go.
|
runtime: clear tiny alloc cache in mark term, not sweep term
The tiny alloc cache is maintained in a pointer from non-GC'd memory
(mcache) to heap memory and hence must be handled carefully.
Currently we clear the tiny alloc cache during sweep termination and,
if it is assigned to a non-nil value during concurrent marking, we
depend on a write barrier to keep the new value alive. However, while
the compiler currently always generates this write barrier, we're
treading on thin ice because write barriers may not happen for writes
to non-heap memory (e.g., typedmemmove). Without this lucky write
barrier, the GC may free a current tiny block while it's still
reachable by the tiny allocator, leading to later memory corruption.
Change this code so that, rather than depending on the write barrier,
we simply clear the tiny cache during mark termination when we're
clearing all of the other mcaches. If the current tiny block is
reachable from regular pointers, it will be retained; if it isn't
reachable from regular pointers, it may be freed, but that's okay
because there won't be any pointers in non-GC'd memory to it.
Change-Id: I8230980d8612c35c2997b9705641a1f9f865f879
Reviewed-on: https://go-review.googlesource.com/16962
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2015-11-16 15:20:59 -05:00
|
|
|
|
|
|
|
|
// tiny points to the beginning of the current tiny block, or
|
|
|
|
|
// nil if there is no current tiny block.
|
|
|
|
|
//
|
|
|
|
|
// tiny is a heap pointer. Since mcache is in non-GC'd memory,
|
|
|
|
|
// we handle it by clearing it in releaseAll during mark
|
|
|
|
|
// termination.
|
2020-08-04 17:29:03 +00:00
|
|
|
//
|
|
|
|
|
// tinyAllocs is the number of tiny allocations performed
|
|
|
|
|
// by the P that owns this mcache.
|
2020-07-23 22:16:46 +00:00
|
|
|
tiny uintptr
|
|
|
|
|
tinyoffset uintptr
|
2020-08-04 17:29:03 +00:00
|
|
|
tinyAllocs uintptr
|
2015-02-19 13:38:46 -05:00
|
|
|
|
|
|
|
|
// The rest is not accessed on every malloc.
|
2016-02-09 17:53:07 -05:00
|
|
|
|
runtime: add runtime.freegc to reduce GC work
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
This CL adds runtime.freegc:
func freegc(ptr unsafe.Pointer, uintptr size, noscan bool)
Memory freed via runtime.freegc is made immediately reusable for
the next allocation in the same size class, without waiting for a
GC cycle, and hence can dramatically reduce pressure on the GC. A sample
microbenchmark included below shows strings.Builder operating roughly
2x faster.
An experimental modification to reflect to use runtime.freegc
and then using that reflect with json/v2 gave reported memory
allocation reductions of -43.7%, -32.9%, -21.9%, -22.0%, -1.0%
for the 5 official real-world unmarshalling benchmarks from
go-json-experiment/jsonbench by the authors of json/v2, covering
the CanadaGeometry through TwitterStatus datasets.
Note: there is no intent to modify the standard library to have
explicit calls to runtime.freegc, and of course such an ability
would never be exposed to end-user code.
Later CLs in this stack teach the compiler how to automatically
insert runtime.freegc calls when it can prove it is safe to do so.
(The reflect modification and other experimental changes to
the standard library were just that -- experiments. It was
very helpful while initially developing runtime.freegc to see
more complex uses and closer-to-real-world benchmark results
prior to updating the compiler.)
This CL only addresses noscan span classes (heap objects without
pointers), such as the backing memory for a []byte or string. A
follow-on CL adds support for heap objects with pointers.
If we update strings.Builder to explicitly call runtime.freegc on its
internal buf after a resize operation (but without freeing the usually
final incarnation of buf that will be returned to the user as a string),
we can see some nice benchmark results on the existing strings
benchmarks that call Builder.Write N times and then call Builder.String.
Here, the (uncommon) case of a single Builder.Write is not helped (given
it never resizes after first alloc if there is only one Write), but the
impact grows such that it is up to ~2x faster as there are more resize
operations due to more strings.Builder.Write calls:
│ disabled.out │ new-free-20.txt │
│ sec/op │ sec/op vs base │
BuildString_Builder/1Write_36Bytes_NoGrow-4 55.82n ± 2% 55.86n ± 2% ~ (p=0.794 n=20)
BuildString_Builder/2Write_36Bytes_NoGrow-4 125.2n ± 2% 115.4n ± 1% -7.86% (p=0.000 n=20)
BuildString_Builder/3Write_36Bytes_NoGrow-4 224.0n ± 1% 188.2n ± 2% -16.00% (p=0.000 n=20)
BuildString_Builder/5Write_36Bytes_NoGrow-4 239.1n ± 9% 205.1n ± 1% -14.20% (p=0.000 n=20)
BuildString_Builder/8Write_36Bytes_NoGrow-4 422.8n ± 3% 325.4n ± 1% -23.04% (p=0.000 n=20)
BuildString_Builder/10Write_36Bytes_NoGrow-4 436.9n ± 2% 342.3n ± 1% -21.64% (p=0.000 n=20)
BuildString_Builder/100Write_36Bytes_NoGrow-4 4.403µ ± 1% 2.381µ ± 2% -45.91% (p=0.000 n=20)
BuildString_Builder/1000Write_36Bytes_NoGrow-4 48.28µ ± 2% 21.38µ ± 2% -55.71% (p=0.000 n=20)
See the design document for more discussion of the strings.Builder case.
For testing, we add tests that attempt to exercise different aspects
of the underlying freegc and mallocgc behavior on the reuse path.
Validating the assist credit manipulations turned out to be subtle,
so a test for that is added in the next CL. There are also
invariant checks added, controlled by consts (primarily the
doubleCheckReusable const currently).
This CL also adds support in runtime.freegc for GODEBUG=clobberfree=1
to immediately overwrite freed memory with 0xdeadbeef, which
can help a higher-level test fail faster in the event of a bug,
and also the GC specifically looks for that pattern and throws
a fatal error if it unexpectedly finds it.
A later CL (currently experimental) adds GODEBUG=clobberfree=2,
which uses mprotect (or VirtualProtect on Windows) to set
freed memory to fault if read or written, until the runtime
later unprotects the memory on the mallocgc reuse path.
For the cases where a normal allocation is happening without any reuse,
some initial microbenchmarks suggest the impact of these changes could
be small to negligible (at least with GOAMD64=v3):
goos: linux
goarch: amd64
pkg: runtime
cpu: AMD EPYC 7B13
│ base-512M-v3.bench │ ps16-512M-goamd64-v3.bench │
│ sec/op │ sec/op vs base │
Malloc8-16 11.01n ± 1% 10.94n ± 1% -0.68% (p=0.038 n=20)
Malloc16-16 17.15n ± 1% 17.05n ± 0% -0.55% (p=0.007 n=20)
Malloc32-16 18.65n ± 1% 18.42n ± 0% -1.26% (p=0.000 n=20)
MallocTypeInfo8-16 18.63n ± 0% 18.36n ± 0% -1.45% (p=0.000 n=20)
MallocTypeInfo16-16 22.32n ± 0% 22.65n ± 0% +1.50% (p=0.000 n=20)
MallocTypeInfo32-16 23.37n ± 0% 23.89n ± 0% +2.23% (p=0.000 n=20)
geomean 18.02n 18.01n -0.05%
These last benchmark results include the runtime updates to support
span classes with pointers (which was originally part of this CL,
but later split out for ease of review).
Updates #74299
Change-Id: Icceaa0f79f85c70cd1a718f9a4e7f0cf3d77803c
Reviewed-on: https://go-review.googlesource.com/c/go/+/673695
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2025-11-04 09:33:17 -05:00
|
|
|
// alloc contains spans to allocate from, indexed by spanClass.
|
|
|
|
|
alloc [numSpanClasses]*mspan
|
|
|
|
|
|
|
|
|
|
// TODO(thepudds): better to interleave alloc and reusableScan/reusableNoscan so that
|
|
|
|
|
// a single malloc call can often access both in the same cache line for a given spanClass.
|
|
|
|
|
// It's not interleaved right now in part to have slightly smaller diff, and might be
|
|
|
|
|
// negligible effect on current microbenchmarks.
|
|
|
|
|
|
|
|
|
|
// reusableNoscan contains linked lists of reusable noscan heap objects, indexed by spanClass.
|
|
|
|
|
// The next pointers are stored in the first word of the heap objects.
|
|
|
|
|
reusableNoscan [numSpanClasses]gclinkptr
|
2015-02-19 13:38:46 -05:00
|
|
|
|
|
|
|
|
stackcache [_NumStackOrders]stackfreelist
|
|
|
|
|
|
2018-08-23 13:14:19 -04:00
|
|
|
// flushGen indicates the sweepgen during which this mcache
|
|
|
|
|
// was last flushed. If flushGen != mheap_.sweepgen, the spans
|
2025-11-07 10:58:08 +00:00
|
|
|
// in this mcache are stale and need to be flushed so they
|
2018-08-23 13:14:19 -04:00
|
|
|
// can be swept. This is done in acquirep.
|
2022-08-25 11:10:52 +08:00
|
|
|
flushGen atomic.Uint32
|
2015-02-19 13:38:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A gclink is a node in a linked list of blocks, like mlink,
|
|
|
|
|
// but it is opaque to the garbage collector.
|
|
|
|
|
// The GC does not trace the pointers during collection,
|
|
|
|
|
// and the compiler does not emit write barriers for assignments
|
|
|
|
|
// of gclinkptr values. Code should store references to gclinks
|
|
|
|
|
// as gclinkptr, not as *gclink.
|
|
|
|
|
type gclink struct {
|
|
|
|
|
next gclinkptr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A gclinkptr is a pointer to a gclink, but it is opaque
|
|
|
|
|
// to the garbage collector.
|
|
|
|
|
type gclinkptr uintptr
|
|
|
|
|
|
|
|
|
|
// ptr returns the *gclink form of p.
|
|
|
|
|
// The result should be used for accessing fields, not stored
|
|
|
|
|
// in other data structures.
|
|
|
|
|
func (p gclinkptr) ptr() *gclink {
|
|
|
|
|
return (*gclink)(unsafe.Pointer(p))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type stackfreelist struct {
|
|
|
|
|
list gclinkptr // linked list of free stacks
|
|
|
|
|
size uintptr // total size of stacks in list
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-05 19:26:25 +00:00
|
|
|
// dummy mspan that contains no free objects.
|
2014-11-11 17:05:02 -05:00
|
|
|
var emptymspan mspan
|
|
|
|
|
|
|
|
|
|
func allocmcache() *mcache {
|
2019-05-17 14:48:04 +00:00
|
|
|
var c *mcache
|
|
|
|
|
systemstack(func() {
|
|
|
|
|
lock(&mheap_.lock)
|
|
|
|
|
c = (*mcache)(mheap_.cachealloc.alloc())
|
2022-08-25 11:10:52 +08:00
|
|
|
c.flushGen.Store(mheap_.sweepgen)
|
2019-05-17 14:48:04 +00:00
|
|
|
unlock(&mheap_.lock)
|
|
|
|
|
})
|
2016-02-09 17:53:07 -05:00
|
|
|
for i := range c.alloc {
|
2014-11-11 17:05:02 -05:00
|
|
|
c.alloc[i] = &emptymspan
|
|
|
|
|
}
|
2020-07-24 19:58:31 +00:00
|
|
|
c.nextSample = nextSample()
|
runtime: add runtime.freegc to reduce GC work
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
This CL adds runtime.freegc:
func freegc(ptr unsafe.Pointer, uintptr size, noscan bool)
Memory freed via runtime.freegc is made immediately reusable for
the next allocation in the same size class, without waiting for a
GC cycle, and hence can dramatically reduce pressure on the GC. A sample
microbenchmark included below shows strings.Builder operating roughly
2x faster.
An experimental modification to reflect to use runtime.freegc
and then using that reflect with json/v2 gave reported memory
allocation reductions of -43.7%, -32.9%, -21.9%, -22.0%, -1.0%
for the 5 official real-world unmarshalling benchmarks from
go-json-experiment/jsonbench by the authors of json/v2, covering
the CanadaGeometry through TwitterStatus datasets.
Note: there is no intent to modify the standard library to have
explicit calls to runtime.freegc, and of course such an ability
would never be exposed to end-user code.
Later CLs in this stack teach the compiler how to automatically
insert runtime.freegc calls when it can prove it is safe to do so.
(The reflect modification and other experimental changes to
the standard library were just that -- experiments. It was
very helpful while initially developing runtime.freegc to see
more complex uses and closer-to-real-world benchmark results
prior to updating the compiler.)
This CL only addresses noscan span classes (heap objects without
pointers), such as the backing memory for a []byte or string. A
follow-on CL adds support for heap objects with pointers.
If we update strings.Builder to explicitly call runtime.freegc on its
internal buf after a resize operation (but without freeing the usually
final incarnation of buf that will be returned to the user as a string),
we can see some nice benchmark results on the existing strings
benchmarks that call Builder.Write N times and then call Builder.String.
Here, the (uncommon) case of a single Builder.Write is not helped (given
it never resizes after first alloc if there is only one Write), but the
impact grows such that it is up to ~2x faster as there are more resize
operations due to more strings.Builder.Write calls:
│ disabled.out │ new-free-20.txt │
│ sec/op │ sec/op vs base │
BuildString_Builder/1Write_36Bytes_NoGrow-4 55.82n ± 2% 55.86n ± 2% ~ (p=0.794 n=20)
BuildString_Builder/2Write_36Bytes_NoGrow-4 125.2n ± 2% 115.4n ± 1% -7.86% (p=0.000 n=20)
BuildString_Builder/3Write_36Bytes_NoGrow-4 224.0n ± 1% 188.2n ± 2% -16.00% (p=0.000 n=20)
BuildString_Builder/5Write_36Bytes_NoGrow-4 239.1n ± 9% 205.1n ± 1% -14.20% (p=0.000 n=20)
BuildString_Builder/8Write_36Bytes_NoGrow-4 422.8n ± 3% 325.4n ± 1% -23.04% (p=0.000 n=20)
BuildString_Builder/10Write_36Bytes_NoGrow-4 436.9n ± 2% 342.3n ± 1% -21.64% (p=0.000 n=20)
BuildString_Builder/100Write_36Bytes_NoGrow-4 4.403µ ± 1% 2.381µ ± 2% -45.91% (p=0.000 n=20)
BuildString_Builder/1000Write_36Bytes_NoGrow-4 48.28µ ± 2% 21.38µ ± 2% -55.71% (p=0.000 n=20)
See the design document for more discussion of the strings.Builder case.
For testing, we add tests that attempt to exercise different aspects
of the underlying freegc and mallocgc behavior on the reuse path.
Validating the assist credit manipulations turned out to be subtle,
so a test for that is added in the next CL. There are also
invariant checks added, controlled by consts (primarily the
doubleCheckReusable const currently).
This CL also adds support in runtime.freegc for GODEBUG=clobberfree=1
to immediately overwrite freed memory with 0xdeadbeef, which
can help a higher-level test fail faster in the event of a bug,
and also the GC specifically looks for that pattern and throws
a fatal error if it unexpectedly finds it.
A later CL (currently experimental) adds GODEBUG=clobberfree=2,
which uses mprotect (or VirtualProtect on Windows) to set
freed memory to fault if read or written, until the runtime
later unprotects the memory on the mallocgc reuse path.
For the cases where a normal allocation is happening without any reuse,
some initial microbenchmarks suggest the impact of these changes could
be small to negligible (at least with GOAMD64=v3):
goos: linux
goarch: amd64
pkg: runtime
cpu: AMD EPYC 7B13
│ base-512M-v3.bench │ ps16-512M-goamd64-v3.bench │
│ sec/op │ sec/op vs base │
Malloc8-16 11.01n ± 1% 10.94n ± 1% -0.68% (p=0.038 n=20)
Malloc16-16 17.15n ± 1% 17.05n ± 0% -0.55% (p=0.007 n=20)
Malloc32-16 18.65n ± 1% 18.42n ± 0% -1.26% (p=0.000 n=20)
MallocTypeInfo8-16 18.63n ± 0% 18.36n ± 0% -1.45% (p=0.000 n=20)
MallocTypeInfo16-16 22.32n ± 0% 22.65n ± 0% +1.50% (p=0.000 n=20)
MallocTypeInfo32-16 23.37n ± 0% 23.89n ± 0% +2.23% (p=0.000 n=20)
geomean 18.02n 18.01n -0.05%
These last benchmark results include the runtime updates to support
span classes with pointers (which was originally part of this CL,
but later split out for ease of review).
Updates #74299
Change-Id: Icceaa0f79f85c70cd1a718f9a4e7f0cf3d77803c
Reviewed-on: https://go-review.googlesource.com/c/go/+/673695
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2025-11-04 09:33:17 -05:00
|
|
|
|
2014-11-11 17:05:02 -05:00
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-23 21:02:05 +00:00
|
|
|
// freemcache releases resources associated with this
|
|
|
|
|
// mcache and puts the object onto a free list.
|
|
|
|
|
//
|
|
|
|
|
// In some cases there is no way to simply release
|
|
|
|
|
// resources, such as statistics, so donate them to
|
|
|
|
|
// a different mcache (the recipient).
|
2020-08-04 17:29:03 +00:00
|
|
|
func freemcache(c *mcache) {
|
[dev.cc] runtime: delete scalararg, ptrarg; rename onM to systemstack
Scalararg and ptrarg are not "signal safe".
Go code filling them out can be interrupted by a signal,
and then the signal handler runs, and if it also ends up
in Go code that uses scalararg or ptrarg, now the old
values have been smashed.
For the pieces of code that do need to run in a signal handler,
we introduced onM_signalok, which is really just onM
except that the _signalok is meant to convey that the caller
asserts that scalarg and ptrarg will be restored to their old
values after the call (instead of the usual behavior, zeroing them).
Scalararg and ptrarg are also untyped and therefore error-prone.
Go code can always pass a closure instead of using scalararg
and ptrarg; they were only really necessary for C code.
And there's no more C code.
For all these reasons, delete scalararg and ptrarg, converting
the few remaining references to use closures.
Once those are gone, there is no need for a distinction between
onM and onM_signalok, so replace both with a single function
equivalent to the current onM_signalok (that is, it can be called
on any of the curg, g0, and gsignal stacks).
The name onM and the phrase 'm stack' are misnomers,
because on most system an M has two system stacks:
the main thread stack and the signal handling stack.
Correct the misnomer by naming the replacement function systemstack.
Fix a few references to "M stack" in code.
The main motivation for this change is to eliminate scalararg/ptrarg.
Rick and I have already seen them cause problems because
the calling sequence m.ptrarg[0] = p is a heap pointer assignment,
so it gets a write barrier. The write barrier also uses onM, so it has
all the same problems as if it were being invoked by a signal handler.
We worked around this by saving and restoring the old values
and by calling onM_signalok, but there's no point in keeping this nice
home for bugs around any longer.
This CL also changes funcline to return the file name as a result
instead of filling in a passed-in *string. (The *string signature is
left over from when the code was written in and called from C.)
That's arguably an unrelated change, except that once I had done
the ptrarg/scalararg/onM cleanup I started getting false positives
about the *string argument escaping (not allowed in package runtime).
The compiler is wrong, but the easiest fix is to write the code like
Go code instead of like C code. I am a bit worried that the compiler
is wrong because of some use of uninitialized memory in the escape
analysis. If that's the reason, it will go away when we convert the
compiler to Go. (And if not, we'll debug it the next time.)
LGTM=khr
R=r, khr
CC=austin, golang-codereviews, iant, rlh
https://golang.org/cl/174950043
2014-11-12 14:54:31 -05:00
|
|
|
systemstack(func() {
|
2015-11-11 16:13:51 -08:00
|
|
|
c.releaseAll()
|
2014-11-11 17:05:02 -05:00
|
|
|
stackcache_clear(c)
|
2014-11-15 08:00:38 -05:00
|
|
|
|
|
|
|
|
// NOTE(rsc,rlh): If gcworkbuffree comes back, we need to coordinate
|
|
|
|
|
// with the stealing of gcworkbufs during garbage collection to avoid
|
|
|
|
|
// a race where the workbuf is double-freed.
|
|
|
|
|
// gcworkbuffree(c.gcworkbuf)
|
|
|
|
|
|
2014-11-11 17:05:02 -05:00
|
|
|
lock(&mheap_.lock)
|
2015-11-11 16:13:51 -08:00
|
|
|
mheap_.cachealloc.free(unsafe.Pointer(c))
|
2014-11-11 17:05:02 -05:00
|
|
|
unlock(&mheap_.lock)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-03 20:08:25 +00:00
|
|
|
// getMCache is a convenience function which tries to obtain an mcache.
|
|
|
|
|
//
|
2020-11-02 16:58:38 +00:00
|
|
|
// Returns nil if we're not bootstrapping or we don't have a P. The caller's
|
|
|
|
|
// P must not change, so we must be in a non-preemptible state.
|
2021-09-11 20:53:24 +08:00
|
|
|
func getMCache(mp *m) *mcache {
|
2020-08-03 20:08:25 +00:00
|
|
|
// Grab the mcache, since that's where stats live.
|
2021-09-11 20:53:24 +08:00
|
|
|
pp := mp.p.ptr()
|
2020-08-03 20:08:25 +00:00
|
|
|
var c *mcache
|
|
|
|
|
if pp == nil {
|
|
|
|
|
// We will be called without a P while bootstrapping,
|
|
|
|
|
// in which case we use mcache0, which is set in mallocinit.
|
|
|
|
|
// mcache0 is cleared when bootstrapping is complete,
|
|
|
|
|
// by procresize.
|
|
|
|
|
c = mcache0
|
|
|
|
|
} else {
|
|
|
|
|
c = pp.mcache
|
|
|
|
|
}
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-22 15:59:01 -04:00
|
|
|
// 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.
|
|
|
|
|
//
|
|
|
|
|
// Must run in a non-preemptible context since otherwise the owner of
|
|
|
|
|
// c could change.
|
2017-10-09 10:54:22 +01:00
|
|
|
func (c *mcache) refill(spc spanClass) {
|
2014-11-11 17:05:02 -05:00
|
|
|
// Return the current cached span to the central lists.
|
2016-02-09 17:53:07 -05:00
|
|
|
s := c.alloc[spc]
|
2016-02-11 13:57:58 -05:00
|
|
|
|
2022-11-16 17:32:08 -05:00
|
|
|
if s.allocCount != s.nelems {
|
2016-02-11 13:57:58 -05:00
|
|
|
throw("refill of span with free space remaining")
|
2014-11-11 17:05:02 -05:00
|
|
|
}
|
runtime: add runtime.freegc to reduce GC work
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
This CL adds runtime.freegc:
func freegc(ptr unsafe.Pointer, uintptr size, noscan bool)
Memory freed via runtime.freegc is made immediately reusable for
the next allocation in the same size class, without waiting for a
GC cycle, and hence can dramatically reduce pressure on the GC. A sample
microbenchmark included below shows strings.Builder operating roughly
2x faster.
An experimental modification to reflect to use runtime.freegc
and then using that reflect with json/v2 gave reported memory
allocation reductions of -43.7%, -32.9%, -21.9%, -22.0%, -1.0%
for the 5 official real-world unmarshalling benchmarks from
go-json-experiment/jsonbench by the authors of json/v2, covering
the CanadaGeometry through TwitterStatus datasets.
Note: there is no intent to modify the standard library to have
explicit calls to runtime.freegc, and of course such an ability
would never be exposed to end-user code.
Later CLs in this stack teach the compiler how to automatically
insert runtime.freegc calls when it can prove it is safe to do so.
(The reflect modification and other experimental changes to
the standard library were just that -- experiments. It was
very helpful while initially developing runtime.freegc to see
more complex uses and closer-to-real-world benchmark results
prior to updating the compiler.)
This CL only addresses noscan span classes (heap objects without
pointers), such as the backing memory for a []byte or string. A
follow-on CL adds support for heap objects with pointers.
If we update strings.Builder to explicitly call runtime.freegc on its
internal buf after a resize operation (but without freeing the usually
final incarnation of buf that will be returned to the user as a string),
we can see some nice benchmark results on the existing strings
benchmarks that call Builder.Write N times and then call Builder.String.
Here, the (uncommon) case of a single Builder.Write is not helped (given
it never resizes after first alloc if there is only one Write), but the
impact grows such that it is up to ~2x faster as there are more resize
operations due to more strings.Builder.Write calls:
│ disabled.out │ new-free-20.txt │
│ sec/op │ sec/op vs base │
BuildString_Builder/1Write_36Bytes_NoGrow-4 55.82n ± 2% 55.86n ± 2% ~ (p=0.794 n=20)
BuildString_Builder/2Write_36Bytes_NoGrow-4 125.2n ± 2% 115.4n ± 1% -7.86% (p=0.000 n=20)
BuildString_Builder/3Write_36Bytes_NoGrow-4 224.0n ± 1% 188.2n ± 2% -16.00% (p=0.000 n=20)
BuildString_Builder/5Write_36Bytes_NoGrow-4 239.1n ± 9% 205.1n ± 1% -14.20% (p=0.000 n=20)
BuildString_Builder/8Write_36Bytes_NoGrow-4 422.8n ± 3% 325.4n ± 1% -23.04% (p=0.000 n=20)
BuildString_Builder/10Write_36Bytes_NoGrow-4 436.9n ± 2% 342.3n ± 1% -21.64% (p=0.000 n=20)
BuildString_Builder/100Write_36Bytes_NoGrow-4 4.403µ ± 1% 2.381µ ± 2% -45.91% (p=0.000 n=20)
BuildString_Builder/1000Write_36Bytes_NoGrow-4 48.28µ ± 2% 21.38µ ± 2% -55.71% (p=0.000 n=20)
See the design document for more discussion of the strings.Builder case.
For testing, we add tests that attempt to exercise different aspects
of the underlying freegc and mallocgc behavior on the reuse path.
Validating the assist credit manipulations turned out to be subtle,
so a test for that is added in the next CL. There are also
invariant checks added, controlled by consts (primarily the
doubleCheckReusable const currently).
This CL also adds support in runtime.freegc for GODEBUG=clobberfree=1
to immediately overwrite freed memory with 0xdeadbeef, which
can help a higher-level test fail faster in the event of a bug,
and also the GC specifically looks for that pattern and throws
a fatal error if it unexpectedly finds it.
A later CL (currently experimental) adds GODEBUG=clobberfree=2,
which uses mprotect (or VirtualProtect on Windows) to set
freed memory to fault if read or written, until the runtime
later unprotects the memory on the mallocgc reuse path.
For the cases where a normal allocation is happening without any reuse,
some initial microbenchmarks suggest the impact of these changes could
be small to negligible (at least with GOAMD64=v3):
goos: linux
goarch: amd64
pkg: runtime
cpu: AMD EPYC 7B13
│ base-512M-v3.bench │ ps16-512M-goamd64-v3.bench │
│ sec/op │ sec/op vs base │
Malloc8-16 11.01n ± 1% 10.94n ± 1% -0.68% (p=0.038 n=20)
Malloc16-16 17.15n ± 1% 17.05n ± 0% -0.55% (p=0.007 n=20)
Malloc32-16 18.65n ± 1% 18.42n ± 0% -1.26% (p=0.000 n=20)
MallocTypeInfo8-16 18.63n ± 0% 18.36n ± 0% -1.45% (p=0.000 n=20)
MallocTypeInfo16-16 22.32n ± 0% 22.65n ± 0% +1.50% (p=0.000 n=20)
MallocTypeInfo32-16 23.37n ± 0% 23.89n ± 0% +2.23% (p=0.000 n=20)
geomean 18.02n 18.01n -0.05%
These last benchmark results include the runtime updates to support
span classes with pointers (which was originally part of this CL,
but later split out for ease of review).
Updates #74299
Change-Id: Icceaa0f79f85c70cd1a718f9a4e7f0cf3d77803c
Reviewed-on: https://go-review.googlesource.com/c/go/+/673695
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2025-11-04 09:33:17 -05:00
|
|
|
|
|
|
|
|
// TODO(thepudds): we might be able to allow mallocgcTiny to reuse 16 byte objects from spc==5,
|
|
|
|
|
// but for now, just clear our reusable objects for tinySpanClass.
|
|
|
|
|
if spc == tinySpanClass {
|
|
|
|
|
c.reusableNoscan[spc] = 0
|
|
|
|
|
}
|
|
|
|
|
if c.reusableNoscan[spc] != 0 {
|
|
|
|
|
throw("refill of span with reusable pointers remaining on pointer free list")
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-11 17:05:02 -05:00
|
|
|
if s != &emptymspan {
|
2018-08-23 13:14:19 -04:00
|
|
|
// Mark this span as no longer cached.
|
|
|
|
|
if s.sweepgen != mheap_.sweepgen+3 {
|
|
|
|
|
throw("bad sweepgen in refill")
|
|
|
|
|
}
|
2020-02-19 16:37:48 +00:00
|
|
|
mheap_.central[spc].mcentral.uncacheSpan(s)
|
2022-01-10 22:59:26 +00:00
|
|
|
|
|
|
|
|
// Count up how many slots were used and record it.
|
|
|
|
|
stats := memstats.heapStats.acquire()
|
2022-05-03 19:28:25 +00:00
|
|
|
slotsUsed := int64(s.allocCount) - int64(s.allocCountBeforeCache)
|
|
|
|
|
atomic.Xadd64(&stats.smallAllocCount[spc.sizeclass()], slotsUsed)
|
2022-01-10 22:59:26 +00:00
|
|
|
|
|
|
|
|
// Flush tinyAllocs.
|
|
|
|
|
if spc == tinySpanClass {
|
2022-05-03 19:28:25 +00:00
|
|
|
atomic.Xadd64(&stats.tinyAllocCount, int64(c.tinyAllocs))
|
2022-01-10 22:59:26 +00:00
|
|
|
c.tinyAllocs = 0
|
|
|
|
|
}
|
|
|
|
|
memstats.heapStats.release()
|
|
|
|
|
|
2022-03-19 21:46:12 +00:00
|
|
|
// Count the allocs in inconsistent, internal stats.
|
2022-05-03 19:28:25 +00:00
|
|
|
bytesAllocated := slotsUsed * int64(s.elemsize)
|
2022-04-01 22:34:45 +00:00
|
|
|
gcController.totalAlloc.Add(bytesAllocated)
|
2022-03-19 21:46:12 +00:00
|
|
|
|
2022-01-10 22:59:26 +00:00
|
|
|
// Clear the second allocCount just to be safe.
|
|
|
|
|
s.allocCountBeforeCache = 0
|
2014-11-11 17:05:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get a new cached span from the central lists.
|
2016-02-09 17:53:07 -05:00
|
|
|
s = mheap_.central[spc].mcentral.cacheSpan()
|
2014-11-11 17:05:02 -05:00
|
|
|
if s == nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("out of memory")
|
2014-11-11 17:05:02 -05:00
|
|
|
}
|
2016-02-11 13:57:58 -05:00
|
|
|
|
2022-11-16 17:32:08 -05:00
|
|
|
if s.allocCount == s.nelems {
|
2016-02-11 13:57:58 -05:00
|
|
|
throw("span has no free space")
|
2014-11-11 17:05:02 -05:00
|
|
|
}
|
2016-02-11 13:57:58 -05:00
|
|
|
|
2018-08-23 13:14:19 -04:00
|
|
|
// Indicate that this span is cached and prevent asynchronous
|
|
|
|
|
// sweeping in the next sweep phase.
|
|
|
|
|
s.sweepgen = mheap_.sweepgen + 3
|
|
|
|
|
|
2022-01-10 22:59:26 +00:00
|
|
|
// Store the current alloc count for accounting later.
|
|
|
|
|
s.allocCountBeforeCache = s.allocCount
|
runtime: flush local_scan directly and more often
Now that local_scan is the last mcache-based statistic that is flushed
by purgecachedstats, and heap_scan and gcController.revise may be
interacted with concurrently, we don't need to flush heap_scan at
arbitrary locations where the heap is locked, and we don't need
purgecachedstats and cachestats anymore. Instead, we can flush
local_scan at the same time we update heap_live in refill, so the two
updates may share the same revise call.
Clean up unused functions, remove code that would cause the heap to get
locked in the allocSpan when it didn't need to (other than to flush
local_scan), and flush local_scan explicitly in a few important places.
Notably we need to flush local_scan whenever we flush the other stats,
but it doesn't need to be donated anywhere, so have releaseAll do the
flushing. Also, we need to flush local_scan before we set heap_scan at
the end of a GC, which was previously handled by cachestats. Just do so
explicitly -- it's not much code and it becomes a lot more clear why we
need to do so.
Change-Id: I35ac081784df7744d515479896a41d530653692d
Reviewed-on: https://go-review.googlesource.com/c/go/+/246968
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Trust: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
2020-07-23 22:36:58 +00:00
|
|
|
|
2022-07-07 20:01:21 +00:00
|
|
|
// Update heapLive and flush scanAlloc.
|
|
|
|
|
//
|
|
|
|
|
// We have not yet allocated anything new into the span, but we
|
|
|
|
|
// assume that all of its slots will get used, so this makes
|
|
|
|
|
// heapLive an overestimate.
|
|
|
|
|
//
|
|
|
|
|
// When the span gets uncached, we'll fix up this overestimate
|
|
|
|
|
// if necessary (see releaseAll).
|
|
|
|
|
//
|
|
|
|
|
// We pick an overestimate here because an underestimate leads
|
|
|
|
|
// the pacer to believe that it's in better shape than it is,
|
|
|
|
|
// which appears to lead to more memory used. See #53738 for
|
|
|
|
|
// more details.
|
|
|
|
|
usedBytes := uintptr(s.allocCount) * s.elemsize
|
|
|
|
|
gcController.update(int64(s.npages*pageSize)-int64(usedBytes), int64(c.scanAlloc))
|
|
|
|
|
c.scanAlloc = 0
|
|
|
|
|
|
2016-02-09 17:53:07 -05:00
|
|
|
c.alloc[spc] = s
|
2014-11-11 17:05:02 -05:00
|
|
|
}
|
|
|
|
|
|
2020-07-24 19:58:31 +00:00
|
|
|
// allocLarge allocates a span for a large object.
|
runtime: clean up allocation zeroing
Currently, the runtime zeroes allocations in several ways. First, small
object spans are always zeroed if they come from mheap, and their slots
are zeroed later in mallocgc if needed. Second, large object spans
(objects that have their own spans) plumb the need for zeroing down into
mheap. Thirdly, large objects that have no pointers have their zeroing
delayed until after preemption is reenabled, but before returning in
mallocgc.
All of this has two consequences:
1. Spans for small objects that come from mheap are sometimes
unnecessarily zeroed, even if the mallocgc call that created them
doesn't need the object slot to be zeroed.
2. This is all messy and difficult to reason about.
This CL simplifies this code, resolving both (1) and (2). First, it
recognizes that zeroing in mheap is unnecessary for small object spans;
mallocgc and its callees in mcache and mcentral, by design, are *always*
able to deal with non-zeroed spans. They must, for they deal with
recycled spans all the time. Once this fact is made clear, the only
remaining use of zeroing in mheap is for large objects.
As a result, this CL lifts mheap zeroing for large objects into
mallocgc, to parallel all the other codepaths in mallocgc. This is makes
the large object allocation code less surprising.
Next, this CL sets the flag for the delayed zeroing explicitly in the one
case where it matters, and inverts and renames the flag from isZeroed to
delayZeroing.
Finally, it adds a check to make sure that only pointer-free allocations
take the delayed zeroing codepath, as an extra safety measure.
Benchmark results: https://perf.golang.org/search?q=upload:20211028.8
Inspired by tapir.liu@gmail.com's CL 343470.
Change-Id: I7e1296adc19ce8a02c8d93a0a5082aefb2673e8f
Reviewed-on: https://go-review.googlesource.com/c/go/+/359477
Trust: Michael Knyszek <mknyszek@google.com>
Reviewed-by: David Chase <drchase@google.com>
2021-10-28 17:52:22 +00:00
|
|
|
func (c *mcache) allocLarge(size uintptr, noscan bool) *mspan {
|
2025-03-04 19:02:48 +00:00
|
|
|
if size+pageSize < size {
|
2020-07-23 21:10:29 +00:00
|
|
|
throw("out of memory")
|
|
|
|
|
}
|
2025-03-04 19:02:48 +00:00
|
|
|
npages := size >> gc.PageShift
|
|
|
|
|
if size&pageMask != 0 {
|
2020-07-23 21:10:29 +00:00
|
|
|
npages++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deduct credit for this span allocation and sweep if
|
|
|
|
|
// necessary. mHeap_Alloc will also sweep npages, so this only
|
|
|
|
|
// pays the debt down to npage pages.
|
2025-03-04 19:02:48 +00:00
|
|
|
deductSweepCredit(npages*pageSize, npages)
|
2020-07-23 21:10:29 +00:00
|
|
|
|
|
|
|
|
spc := makeSpanClass(0, noscan)
|
runtime: clean up allocation zeroing
Currently, the runtime zeroes allocations in several ways. First, small
object spans are always zeroed if they come from mheap, and their slots
are zeroed later in mallocgc if needed. Second, large object spans
(objects that have their own spans) plumb the need for zeroing down into
mheap. Thirdly, large objects that have no pointers have their zeroing
delayed until after preemption is reenabled, but before returning in
mallocgc.
All of this has two consequences:
1. Spans for small objects that come from mheap are sometimes
unnecessarily zeroed, even if the mallocgc call that created them
doesn't need the object slot to be zeroed.
2. This is all messy and difficult to reason about.
This CL simplifies this code, resolving both (1) and (2). First, it
recognizes that zeroing in mheap is unnecessary for small object spans;
mallocgc and its callees in mcache and mcentral, by design, are *always*
able to deal with non-zeroed spans. They must, for they deal with
recycled spans all the time. Once this fact is made clear, the only
remaining use of zeroing in mheap is for large objects.
As a result, this CL lifts mheap zeroing for large objects into
mallocgc, to parallel all the other codepaths in mallocgc. This is makes
the large object allocation code less surprising.
Next, this CL sets the flag for the delayed zeroing explicitly in the one
case where it matters, and inverts and renames the flag from isZeroed to
delayZeroing.
Finally, it adds a check to make sure that only pointer-free allocations
take the delayed zeroing codepath, as an extra safety measure.
Benchmark results: https://perf.golang.org/search?q=upload:20211028.8
Inspired by tapir.liu@gmail.com's CL 343470.
Change-Id: I7e1296adc19ce8a02c8d93a0a5082aefb2673e8f
Reviewed-on: https://go-review.googlesource.com/c/go/+/359477
Trust: Michael Knyszek <mknyszek@google.com>
Reviewed-by: David Chase <drchase@google.com>
2021-10-28 17:52:22 +00:00
|
|
|
s := mheap_.alloc(npages, spc)
|
2020-07-23 21:10:29 +00:00
|
|
|
if s == nil {
|
|
|
|
|
throw("out of memory")
|
|
|
|
|
}
|
2022-03-19 21:46:12 +00:00
|
|
|
|
|
|
|
|
// Count the alloc in consistent, external stats.
|
2020-11-02 19:03:16 +00:00
|
|
|
stats := memstats.heapStats.acquire()
|
2022-05-03 19:28:25 +00:00
|
|
|
atomic.Xadd64(&stats.largeAlloc, int64(npages*pageSize))
|
|
|
|
|
atomic.Xadd64(&stats.largeAllocCount, 1)
|
2020-11-02 19:03:16 +00:00
|
|
|
memstats.heapStats.release()
|
2020-07-23 21:10:29 +00:00
|
|
|
|
2022-03-19 21:46:12 +00:00
|
|
|
// Count the alloc in inconsistent, internal stats.
|
2022-04-01 22:34:45 +00:00
|
|
|
gcController.totalAlloc.Add(int64(npages * pageSize))
|
2022-03-19 21:46:12 +00:00
|
|
|
|
2021-04-11 18:29:03 +00:00
|
|
|
// Update heapLive.
|
|
|
|
|
gcController.update(int64(s.npages*pageSize), 0)
|
2020-07-23 21:10:29 +00:00
|
|
|
|
|
|
|
|
// Put the large span in the mcentral swept list so that it's
|
|
|
|
|
// visible to the background sweeper.
|
|
|
|
|
mheap_.central[spc].mcentral.fullSwept(mheap_.sweepgen).push(s)
|
2025-06-18 17:42:16 +00:00
|
|
|
|
|
|
|
|
// Adjust s.limit down to the object-containing part of the span.
|
|
|
|
|
//
|
|
|
|
|
// This is just to create a slightly tighter bound on the limit.
|
|
|
|
|
// It's totally OK if the garbage collector, in particular
|
|
|
|
|
// conservative scanning, can temporarily observes an inflated
|
|
|
|
|
// limit. It will simply mark the whole object or just skip it
|
|
|
|
|
// since we're in the mark phase anyway.
|
2020-07-23 21:10:29 +00:00
|
|
|
s.limit = s.base() + size
|
2024-09-18 02:38:45 +00:00
|
|
|
s.initHeapBits()
|
runtime: clean up allocation zeroing
Currently, the runtime zeroes allocations in several ways. First, small
object spans are always zeroed if they come from mheap, and their slots
are zeroed later in mallocgc if needed. Second, large object spans
(objects that have their own spans) plumb the need for zeroing down into
mheap. Thirdly, large objects that have no pointers have their zeroing
delayed until after preemption is reenabled, but before returning in
mallocgc.
All of this has two consequences:
1. Spans for small objects that come from mheap are sometimes
unnecessarily zeroed, even if the mallocgc call that created them
doesn't need the object slot to be zeroed.
2. This is all messy and difficult to reason about.
This CL simplifies this code, resolving both (1) and (2). First, it
recognizes that zeroing in mheap is unnecessary for small object spans;
mallocgc and its callees in mcache and mcentral, by design, are *always*
able to deal with non-zeroed spans. They must, for they deal with
recycled spans all the time. Once this fact is made clear, the only
remaining use of zeroing in mheap is for large objects.
As a result, this CL lifts mheap zeroing for large objects into
mallocgc, to parallel all the other codepaths in mallocgc. This is makes
the large object allocation code less surprising.
Next, this CL sets the flag for the delayed zeroing explicitly in the one
case where it matters, and inverts and renames the flag from isZeroed to
delayZeroing.
Finally, it adds a check to make sure that only pointer-free allocations
take the delayed zeroing codepath, as an extra safety measure.
Benchmark results: https://perf.golang.org/search?q=upload:20211028.8
Inspired by tapir.liu@gmail.com's CL 343470.
Change-Id: I7e1296adc19ce8a02c8d93a0a5082aefb2673e8f
Reviewed-on: https://go-review.googlesource.com/c/go/+/359477
Trust: Michael Knyszek <mknyszek@google.com>
Reviewed-by: David Chase <drchase@google.com>
2021-10-28 17:52:22 +00:00
|
|
|
return s
|
2020-07-23 21:10:29 +00:00
|
|
|
}
|
|
|
|
|
|
2015-11-11 16:13:51 -08:00
|
|
|
func (c *mcache) releaseAll() {
|
2020-07-24 19:58:31 +00:00
|
|
|
// Take this opportunity to flush scanAlloc.
|
2021-04-11 18:29:03 +00:00
|
|
|
scanAlloc := int64(c.scanAlloc)
|
2020-07-24 19:58:31 +00:00
|
|
|
c.scanAlloc = 0
|
runtime: flush local_scan directly and more often
Now that local_scan is the last mcache-based statistic that is flushed
by purgecachedstats, and heap_scan and gcController.revise may be
interacted with concurrently, we don't need to flush heap_scan at
arbitrary locations where the heap is locked, and we don't need
purgecachedstats and cachestats anymore. Instead, we can flush
local_scan at the same time we update heap_live in refill, so the two
updates may share the same revise call.
Clean up unused functions, remove code that would cause the heap to get
locked in the allocSpan when it didn't need to (other than to flush
local_scan), and flush local_scan explicitly in a few important places.
Notably we need to flush local_scan whenever we flush the other stats,
but it doesn't need to be donated anywhere, so have releaseAll do the
flushing. Also, we need to flush local_scan before we set heap_scan at
the end of a GC, which was previously handled by cachestats. Just do so
explicitly -- it's not much code and it becomes a lot more clear why we
need to do so.
Change-Id: I35ac081784df7744d515479896a41d530653692d
Reviewed-on: https://go-review.googlesource.com/c/go/+/246968
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Trust: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
2020-07-23 22:36:58 +00:00
|
|
|
|
2022-07-07 20:01:21 +00:00
|
|
|
sg := mheap_.sweepgen
|
|
|
|
|
dHeapLive := int64(0)
|
2016-02-09 17:53:07 -05:00
|
|
|
for i := range c.alloc {
|
2014-11-11 17:05:02 -05:00
|
|
|
s := c.alloc[i]
|
|
|
|
|
if s != &emptymspan {
|
2022-05-03 19:28:25 +00:00
|
|
|
slotsUsed := int64(s.allocCount) - int64(s.allocCountBeforeCache)
|
2022-03-19 21:46:12 +00:00
|
|
|
s.allocCountBeforeCache = 0
|
|
|
|
|
|
2022-01-10 22:59:26 +00:00
|
|
|
// Adjust smallAllocCount for whatever was allocated.
|
2020-11-02 19:03:16 +00:00
|
|
|
stats := memstats.heapStats.acquire()
|
2022-05-03 19:28:25 +00:00
|
|
|
atomic.Xadd64(&stats.smallAllocCount[spanClass(i).sizeclass()], slotsUsed)
|
2020-11-02 19:03:16 +00:00
|
|
|
memstats.heapStats.release()
|
2022-03-19 21:46:12 +00:00
|
|
|
|
|
|
|
|
// Adjust the actual allocs in inconsistent, internal stats.
|
|
|
|
|
// We assumed earlier that the full span gets allocated.
|
2022-05-03 19:28:25 +00:00
|
|
|
gcController.totalAlloc.Add(slotsUsed * int64(s.elemsize))
|
2022-01-10 22:59:26 +00:00
|
|
|
|
2022-07-07 20:01:21 +00:00
|
|
|
if s.sweepgen != sg+1 {
|
|
|
|
|
// refill conservatively counted unallocated slots in gcController.heapLive.
|
|
|
|
|
// Undo this.
|
|
|
|
|
//
|
|
|
|
|
// If this span was cached before sweep, then gcController.heapLive was totally
|
|
|
|
|
// recomputed since caching this span, so we don't do this for stale spans.
|
2022-11-16 17:32:08 -05:00
|
|
|
dHeapLive -= int64(s.nelems-s.allocCount) * int64(s.elemsize)
|
2022-07-07 20:01:21 +00:00
|
|
|
}
|
|
|
|
|
|
2020-07-23 22:07:44 +00:00
|
|
|
// Release the span to the mcentral.
|
2015-11-11 16:13:51 -08:00
|
|
|
mheap_.central[i].mcentral.uncacheSpan(s)
|
2014-11-11 17:05:02 -05:00
|
|
|
c.alloc[i] = &emptymspan
|
|
|
|
|
}
|
|
|
|
|
}
|
runtime: clear tiny alloc cache in mark term, not sweep term
The tiny alloc cache is maintained in a pointer from non-GC'd memory
(mcache) to heap memory and hence must be handled carefully.
Currently we clear the tiny alloc cache during sweep termination and,
if it is assigned to a non-nil value during concurrent marking, we
depend on a write barrier to keep the new value alive. However, while
the compiler currently always generates this write barrier, we're
treading on thin ice because write barriers may not happen for writes
to non-heap memory (e.g., typedmemmove). Without this lucky write
barrier, the GC may free a current tiny block while it's still
reachable by the tiny allocator, leading to later memory corruption.
Change this code so that, rather than depending on the write barrier,
we simply clear the tiny cache during mark termination when we're
clearing all of the other mcaches. If the current tiny block is
reachable from regular pointers, it will be retained; if it isn't
reachable from regular pointers, it may be freed, but that's okay
because there won't be any pointers in non-GC'd memory to it.
Change-Id: I8230980d8612c35c2997b9705641a1f9f865f879
Reviewed-on: https://go-review.googlesource.com/16962
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2015-11-16 15:20:59 -05:00
|
|
|
// Clear tinyalloc pool.
|
2015-11-16 15:31:50 -05:00
|
|
|
c.tiny = 0
|
runtime: clear tiny alloc cache in mark term, not sweep term
The tiny alloc cache is maintained in a pointer from non-GC'd memory
(mcache) to heap memory and hence must be handled carefully.
Currently we clear the tiny alloc cache during sweep termination and,
if it is assigned to a non-nil value during concurrent marking, we
depend on a write barrier to keep the new value alive. However, while
the compiler currently always generates this write barrier, we're
treading on thin ice because write barriers may not happen for writes
to non-heap memory (e.g., typedmemmove). Without this lucky write
barrier, the GC may free a current tiny block while it's still
reachable by the tiny allocator, leading to later memory corruption.
Change this code so that, rather than depending on the write barrier,
we simply clear the tiny cache during mark termination when we're
clearing all of the other mcaches. If the current tiny block is
reachable from regular pointers, it will be retained; if it isn't
reachable from regular pointers, it may be freed, but that's okay
because there won't be any pointers in non-GC'd memory to it.
Change-Id: I8230980d8612c35c2997b9705641a1f9f865f879
Reviewed-on: https://go-review.googlesource.com/16962
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2015-11-16 15:20:59 -05:00
|
|
|
c.tinyoffset = 0
|
2021-04-21 23:50:58 +00:00
|
|
|
|
|
|
|
|
// Flush tinyAllocs.
|
|
|
|
|
stats := memstats.heapStats.acquire()
|
2022-05-03 19:28:25 +00:00
|
|
|
atomic.Xadd64(&stats.tinyAllocCount, int64(c.tinyAllocs))
|
2020-08-04 17:29:03 +00:00
|
|
|
c.tinyAllocs = 0
|
2021-04-21 23:50:58 +00:00
|
|
|
memstats.heapStats.release()
|
runtime: flush local_scan directly and more often
Now that local_scan is the last mcache-based statistic that is flushed
by purgecachedstats, and heap_scan and gcController.revise may be
interacted with concurrently, we don't need to flush heap_scan at
arbitrary locations where the heap is locked, and we don't need
purgecachedstats and cachestats anymore. Instead, we can flush
local_scan at the same time we update heap_live in refill, so the two
updates may share the same revise call.
Clean up unused functions, remove code that would cause the heap to get
locked in the allocSpan when it didn't need to (other than to flush
local_scan), and flush local_scan explicitly in a few important places.
Notably we need to flush local_scan whenever we flush the other stats,
but it doesn't need to be donated anywhere, so have releaseAll do the
flushing. Also, we need to flush local_scan before we set heap_scan at
the end of a GC, which was previously handled by cachestats. Just do so
explicitly -- it's not much code and it becomes a lot more clear why we
need to do so.
Change-Id: I35ac081784df7744d515479896a41d530653692d
Reviewed-on: https://go-review.googlesource.com/c/go/+/246968
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Trust: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
2020-07-23 22:36:58 +00:00
|
|
|
|
runtime: add runtime.freegc to reduce GC work
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
This CL adds runtime.freegc:
func freegc(ptr unsafe.Pointer, uintptr size, noscan bool)
Memory freed via runtime.freegc is made immediately reusable for
the next allocation in the same size class, without waiting for a
GC cycle, and hence can dramatically reduce pressure on the GC. A sample
microbenchmark included below shows strings.Builder operating roughly
2x faster.
An experimental modification to reflect to use runtime.freegc
and then using that reflect with json/v2 gave reported memory
allocation reductions of -43.7%, -32.9%, -21.9%, -22.0%, -1.0%
for the 5 official real-world unmarshalling benchmarks from
go-json-experiment/jsonbench by the authors of json/v2, covering
the CanadaGeometry through TwitterStatus datasets.
Note: there is no intent to modify the standard library to have
explicit calls to runtime.freegc, and of course such an ability
would never be exposed to end-user code.
Later CLs in this stack teach the compiler how to automatically
insert runtime.freegc calls when it can prove it is safe to do so.
(The reflect modification and other experimental changes to
the standard library were just that -- experiments. It was
very helpful while initially developing runtime.freegc to see
more complex uses and closer-to-real-world benchmark results
prior to updating the compiler.)
This CL only addresses noscan span classes (heap objects without
pointers), such as the backing memory for a []byte or string. A
follow-on CL adds support for heap objects with pointers.
If we update strings.Builder to explicitly call runtime.freegc on its
internal buf after a resize operation (but without freeing the usually
final incarnation of buf that will be returned to the user as a string),
we can see some nice benchmark results on the existing strings
benchmarks that call Builder.Write N times and then call Builder.String.
Here, the (uncommon) case of a single Builder.Write is not helped (given
it never resizes after first alloc if there is only one Write), but the
impact grows such that it is up to ~2x faster as there are more resize
operations due to more strings.Builder.Write calls:
│ disabled.out │ new-free-20.txt │
│ sec/op │ sec/op vs base │
BuildString_Builder/1Write_36Bytes_NoGrow-4 55.82n ± 2% 55.86n ± 2% ~ (p=0.794 n=20)
BuildString_Builder/2Write_36Bytes_NoGrow-4 125.2n ± 2% 115.4n ± 1% -7.86% (p=0.000 n=20)
BuildString_Builder/3Write_36Bytes_NoGrow-4 224.0n ± 1% 188.2n ± 2% -16.00% (p=0.000 n=20)
BuildString_Builder/5Write_36Bytes_NoGrow-4 239.1n ± 9% 205.1n ± 1% -14.20% (p=0.000 n=20)
BuildString_Builder/8Write_36Bytes_NoGrow-4 422.8n ± 3% 325.4n ± 1% -23.04% (p=0.000 n=20)
BuildString_Builder/10Write_36Bytes_NoGrow-4 436.9n ± 2% 342.3n ± 1% -21.64% (p=0.000 n=20)
BuildString_Builder/100Write_36Bytes_NoGrow-4 4.403µ ± 1% 2.381µ ± 2% -45.91% (p=0.000 n=20)
BuildString_Builder/1000Write_36Bytes_NoGrow-4 48.28µ ± 2% 21.38µ ± 2% -55.71% (p=0.000 n=20)
See the design document for more discussion of the strings.Builder case.
For testing, we add tests that attempt to exercise different aspects
of the underlying freegc and mallocgc behavior on the reuse path.
Validating the assist credit manipulations turned out to be subtle,
so a test for that is added in the next CL. There are also
invariant checks added, controlled by consts (primarily the
doubleCheckReusable const currently).
This CL also adds support in runtime.freegc for GODEBUG=clobberfree=1
to immediately overwrite freed memory with 0xdeadbeef, which
can help a higher-level test fail faster in the event of a bug,
and also the GC specifically looks for that pattern and throws
a fatal error if it unexpectedly finds it.
A later CL (currently experimental) adds GODEBUG=clobberfree=2,
which uses mprotect (or VirtualProtect on Windows) to set
freed memory to fault if read or written, until the runtime
later unprotects the memory on the mallocgc reuse path.
For the cases where a normal allocation is happening without any reuse,
some initial microbenchmarks suggest the impact of these changes could
be small to negligible (at least with GOAMD64=v3):
goos: linux
goarch: amd64
pkg: runtime
cpu: AMD EPYC 7B13
│ base-512M-v3.bench │ ps16-512M-goamd64-v3.bench │
│ sec/op │ sec/op vs base │
Malloc8-16 11.01n ± 1% 10.94n ± 1% -0.68% (p=0.038 n=20)
Malloc16-16 17.15n ± 1% 17.05n ± 0% -0.55% (p=0.007 n=20)
Malloc32-16 18.65n ± 1% 18.42n ± 0% -1.26% (p=0.000 n=20)
MallocTypeInfo8-16 18.63n ± 0% 18.36n ± 0% -1.45% (p=0.000 n=20)
MallocTypeInfo16-16 22.32n ± 0% 22.65n ± 0% +1.50% (p=0.000 n=20)
MallocTypeInfo32-16 23.37n ± 0% 23.89n ± 0% +2.23% (p=0.000 n=20)
geomean 18.02n 18.01n -0.05%
These last benchmark results include the runtime updates to support
span classes with pointers (which was originally part of this CL,
but later split out for ease of review).
Updates #74299
Change-Id: Icceaa0f79f85c70cd1a718f9a4e7f0cf3d77803c
Reviewed-on: https://go-review.googlesource.com/c/go/+/673695
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2025-11-04 09:33:17 -05:00
|
|
|
// Clear the reusable linked lists.
|
|
|
|
|
// For noscan objects, the nodes of the linked lists are the reusable heap objects themselves,
|
|
|
|
|
// so we can simply clear the linked list head pointers.
|
|
|
|
|
// TODO(thepudds): consider having debug logging of a non-empty reusable lists getting cleared,
|
|
|
|
|
// maybe based on the existing debugReusableLog.
|
|
|
|
|
clear(c.reusableNoscan[:])
|
|
|
|
|
|
2022-07-07 20:01:21 +00:00
|
|
|
// Update heapLive and heapScan.
|
|
|
|
|
gcController.update(dHeapLive, scanAlloc)
|
2014-11-11 17:05:02 -05:00
|
|
|
}
|
2018-08-23 13:14:19 -04:00
|
|
|
|
|
|
|
|
// prepareForSweep flushes c if the system has entered a new sweep phase
|
|
|
|
|
// since c was populated. This must happen between the sweep phase
|
|
|
|
|
// starting and the first allocation from c.
|
|
|
|
|
func (c *mcache) prepareForSweep() {
|
|
|
|
|
// Alternatively, instead of making sure we do this on every P
|
|
|
|
|
// between starting the world and allocating on that P, we
|
|
|
|
|
// could leave allocate-black on, allow allocation to continue
|
|
|
|
|
// as usual, use a ragged barrier at the beginning of sweep to
|
|
|
|
|
// ensure all cached spans are swept, and then disable
|
|
|
|
|
// allocate-black. However, with this approach it's difficult
|
|
|
|
|
// to avoid spilling mark bits into the *next* GC cycle.
|
|
|
|
|
sg := mheap_.sweepgen
|
2022-08-25 11:10:52 +08:00
|
|
|
flushGen := c.flushGen.Load()
|
|
|
|
|
if flushGen == sg {
|
2018-08-23 13:14:19 -04:00
|
|
|
return
|
2022-08-25 11:10:52 +08:00
|
|
|
} else if flushGen != sg-2 {
|
|
|
|
|
println("bad flushGen", flushGen, "in prepareForSweep; sweepgen", sg)
|
2018-08-23 13:14:19 -04:00
|
|
|
throw("bad flushGen")
|
|
|
|
|
}
|
|
|
|
|
c.releaseAll()
|
|
|
|
|
stackcache_clear(c)
|
2022-08-25 11:10:52 +08:00
|
|
|
c.flushGen.Store(mheap_.sweepgen) // Synchronizes with gcStart
|
2018-08-23 13:14:19 -04:00
|
|
|
}
|
runtime: add runtime.freegc to reduce GC work
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
This CL adds runtime.freegc:
func freegc(ptr unsafe.Pointer, uintptr size, noscan bool)
Memory freed via runtime.freegc is made immediately reusable for
the next allocation in the same size class, without waiting for a
GC cycle, and hence can dramatically reduce pressure on the GC. A sample
microbenchmark included below shows strings.Builder operating roughly
2x faster.
An experimental modification to reflect to use runtime.freegc
and then using that reflect with json/v2 gave reported memory
allocation reductions of -43.7%, -32.9%, -21.9%, -22.0%, -1.0%
for the 5 official real-world unmarshalling benchmarks from
go-json-experiment/jsonbench by the authors of json/v2, covering
the CanadaGeometry through TwitterStatus datasets.
Note: there is no intent to modify the standard library to have
explicit calls to runtime.freegc, and of course such an ability
would never be exposed to end-user code.
Later CLs in this stack teach the compiler how to automatically
insert runtime.freegc calls when it can prove it is safe to do so.
(The reflect modification and other experimental changes to
the standard library were just that -- experiments. It was
very helpful while initially developing runtime.freegc to see
more complex uses and closer-to-real-world benchmark results
prior to updating the compiler.)
This CL only addresses noscan span classes (heap objects without
pointers), such as the backing memory for a []byte or string. A
follow-on CL adds support for heap objects with pointers.
If we update strings.Builder to explicitly call runtime.freegc on its
internal buf after a resize operation (but without freeing the usually
final incarnation of buf that will be returned to the user as a string),
we can see some nice benchmark results on the existing strings
benchmarks that call Builder.Write N times and then call Builder.String.
Here, the (uncommon) case of a single Builder.Write is not helped (given
it never resizes after first alloc if there is only one Write), but the
impact grows such that it is up to ~2x faster as there are more resize
operations due to more strings.Builder.Write calls:
│ disabled.out │ new-free-20.txt │
│ sec/op │ sec/op vs base │
BuildString_Builder/1Write_36Bytes_NoGrow-4 55.82n ± 2% 55.86n ± 2% ~ (p=0.794 n=20)
BuildString_Builder/2Write_36Bytes_NoGrow-4 125.2n ± 2% 115.4n ± 1% -7.86% (p=0.000 n=20)
BuildString_Builder/3Write_36Bytes_NoGrow-4 224.0n ± 1% 188.2n ± 2% -16.00% (p=0.000 n=20)
BuildString_Builder/5Write_36Bytes_NoGrow-4 239.1n ± 9% 205.1n ± 1% -14.20% (p=0.000 n=20)
BuildString_Builder/8Write_36Bytes_NoGrow-4 422.8n ± 3% 325.4n ± 1% -23.04% (p=0.000 n=20)
BuildString_Builder/10Write_36Bytes_NoGrow-4 436.9n ± 2% 342.3n ± 1% -21.64% (p=0.000 n=20)
BuildString_Builder/100Write_36Bytes_NoGrow-4 4.403µ ± 1% 2.381µ ± 2% -45.91% (p=0.000 n=20)
BuildString_Builder/1000Write_36Bytes_NoGrow-4 48.28µ ± 2% 21.38µ ± 2% -55.71% (p=0.000 n=20)
See the design document for more discussion of the strings.Builder case.
For testing, we add tests that attempt to exercise different aspects
of the underlying freegc and mallocgc behavior on the reuse path.
Validating the assist credit manipulations turned out to be subtle,
so a test for that is added in the next CL. There are also
invariant checks added, controlled by consts (primarily the
doubleCheckReusable const currently).
This CL also adds support in runtime.freegc for GODEBUG=clobberfree=1
to immediately overwrite freed memory with 0xdeadbeef, which
can help a higher-level test fail faster in the event of a bug,
and also the GC specifically looks for that pattern and throws
a fatal error if it unexpectedly finds it.
A later CL (currently experimental) adds GODEBUG=clobberfree=2,
which uses mprotect (or VirtualProtect on Windows) to set
freed memory to fault if read or written, until the runtime
later unprotects the memory on the mallocgc reuse path.
For the cases where a normal allocation is happening without any reuse,
some initial microbenchmarks suggest the impact of these changes could
be small to negligible (at least with GOAMD64=v3):
goos: linux
goarch: amd64
pkg: runtime
cpu: AMD EPYC 7B13
│ base-512M-v3.bench │ ps16-512M-goamd64-v3.bench │
│ sec/op │ sec/op vs base │
Malloc8-16 11.01n ± 1% 10.94n ± 1% -0.68% (p=0.038 n=20)
Malloc16-16 17.15n ± 1% 17.05n ± 0% -0.55% (p=0.007 n=20)
Malloc32-16 18.65n ± 1% 18.42n ± 0% -1.26% (p=0.000 n=20)
MallocTypeInfo8-16 18.63n ± 0% 18.36n ± 0% -1.45% (p=0.000 n=20)
MallocTypeInfo16-16 22.32n ± 0% 22.65n ± 0% +1.50% (p=0.000 n=20)
MallocTypeInfo32-16 23.37n ± 0% 23.89n ± 0% +2.23% (p=0.000 n=20)
geomean 18.02n 18.01n -0.05%
These last benchmark results include the runtime updates to support
span classes with pointers (which was originally part of this CL,
but later split out for ease of review).
Updates #74299
Change-Id: Icceaa0f79f85c70cd1a718f9a4e7f0cf3d77803c
Reviewed-on: https://go-review.googlesource.com/c/go/+/673695
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2025-11-04 09:33:17 -05:00
|
|
|
|
|
|
|
|
// addReusableNoscan adds a noscan object pointer to the reusable pointer free list
|
|
|
|
|
// for a span class.
|
|
|
|
|
func (c *mcache) addReusableNoscan(spc spanClass, ptr uintptr) {
|
|
|
|
|
if !runtimeFreegcEnabled {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add to the reusable pointers free list.
|
|
|
|
|
v := gclinkptr(ptr)
|
|
|
|
|
v.ptr().next = c.reusableNoscan[spc]
|
|
|
|
|
c.reusableNoscan[spc] = v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// hasReusableNoscan reports whether there is a reusable object available for
|
|
|
|
|
// a noscan spc.
|
|
|
|
|
func (c *mcache) hasReusableNoscan(spc spanClass) bool {
|
|
|
|
|
if !runtimeFreegcEnabled {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return c.reusableNoscan[spc] != 0
|
|
|
|
|
}
|