@@ -30,7 +30,8 @@ const minPhysPageSize = 4096
30
30
//go:notinheap
31
31
type mheap struct {
32
32
lock mutex
33
- free mTreap // free treap of spans
33
+ free mTreap // free and non-scavenged spans
34
+ scav mTreap // free and scavenged spans
34
35
busy mSpanList // busy list of spans
35
36
sweepgen uint32 // sweep generation, see comment in mspan
36
37
sweepdone uint32 // all spans are swept
@@ -60,7 +61,7 @@ type mheap struct {
60
61
// on the swept stack.
61
62
sweepSpans [2 ]gcSweepBuf
62
63
63
- // _ uint32 // align uint64 fields on 32-bit for atomics
64
+ _ uint32 // align uint64 fields on 32-bit for atomics
64
65
65
66
// Proportional sweep
66
67
//
@@ -132,7 +133,7 @@ type mheap struct {
132
133
// (the actual arenas). This is only used on 32-bit.
133
134
arena linearAlloc
134
135
135
- //_ uint32 // ensure 64-bit alignment of central
136
+ // _ uint32 // ensure 64-bit alignment of central
136
137
137
138
// central free lists for small size classes.
138
139
// the padding makes sure that the MCentrals are
@@ -840,18 +841,31 @@ func (h *mheap) setSpans(base, npage uintptr, s *mspan) {
840
841
func (h * mheap ) allocSpanLocked (npage uintptr , stat * uint64 ) * mspan {
841
842
var s * mspan
842
843
843
- // Best fit in the treap of spans.
844
+ // First, attempt to allocate from free spans, then from
845
+ // scavenged spans, looking for best fit in each.
844
846
s = h .free .remove (npage )
845
- if s == nil {
846
- if ! h .grow (npage ) {
847
- return nil
848
- }
849
- s = h .free .remove (npage )
850
- if s == nil {
851
- return nil
852
- }
847
+ if s != nil {
848
+ goto HaveSpan
849
+ }
850
+ s = h .scav .remove (npage )
851
+ if s != nil {
852
+ goto HaveSpan
853
+ }
854
+ // On failure, grow the heap and try again.
855
+ if ! h .grow (npage ) {
856
+ return nil
857
+ }
858
+ s = h .free .remove (npage )
859
+ if s != nil {
860
+ goto HaveSpan
861
+ }
862
+ s = h .scav .remove (npage )
863
+ if s != nil {
864
+ goto HaveSpan
853
865
}
866
+ return nil
854
867
868
+ HaveSpan:
855
869
// Mark span in use.
856
870
if s .state != mSpanFree {
857
871
throw ("MHeap_AllocLocked - MSpan not free" )
@@ -1002,46 +1016,107 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i
1002
1016
if unusedsince == 0 {
1003
1017
s .unusedsince = nanotime ()
1004
1018
}
1005
- s .npreleased = 0
1019
+
1020
+ // We scavenge s at the end after coalescing if s or anything
1021
+ // it merged with is marked scavenged.
1022
+ needsScavenge := s .npreleased != 0
1023
+ prescavenged := s .npreleased * pageSize // number of bytes already scavenged.
1006
1024
1007
1025
// Coalesce with earlier, later spans.
1008
1026
if before := spanOf (s .base () - 1 ); before != nil && before .state == mSpanFree {
1009
1027
// Now adjust s.
1010
1028
s .startAddr = before .startAddr
1011
1029
s .npages += before .npages
1012
- s .npreleased = before .npreleased // absorb released pages
1013
1030
s .needzero |= before .needzero
1014
1031
h .setSpan (before .base (), s )
1032
+ s .npreleased += before .npreleased // absorb released pages
1015
1033
// The size is potentially changing so the treap needs to delete adjacent nodes and
1016
1034
// insert back as a combined node.
1017
- h .free .removeSpan (before )
1035
+ if before .npreleased == 0 {
1036
+ h .free .removeSpan (before )
1037
+ } else {
1038
+ h .scav .removeSpan (before )
1039
+ needsScavenge = true
1040
+ prescavenged += before .npreleased * pageSize
1041
+ }
1018
1042
before .state = mSpanDead
1019
1043
h .spanalloc .free (unsafe .Pointer (before ))
1020
1044
}
1021
1045
1022
1046
// Now check to see if next (greater addresses) span is free and can be coalesced.
1023
1047
if after := spanOf (s .base () + s .npages * pageSize ); after != nil && after .state == mSpanFree {
1024
1048
s .npages += after .npages
1025
- s .npreleased += after .npreleased
1026
1049
s .needzero |= after .needzero
1027
1050
h .setSpan (s .base ()+ s .npages * pageSize - 1 , s )
1028
- h .free .removeSpan (after )
1051
+ if after .npreleased == 0 {
1052
+ h .free .removeSpan (after )
1053
+ } else {
1054
+ h .scav .removeSpan (after )
1055
+ needsScavenge = true
1056
+ prescavenged += after .npreleased * pageSize
1057
+ }
1058
+ s .npreleased += after .npreleased
1029
1059
after .state = mSpanDead
1030
1060
h .spanalloc .free (unsafe .Pointer (after ))
1031
1061
}
1032
1062
1033
- // Insert s into the free treap.
1034
- h .free .insert (s )
1063
+ if needsScavenge {
1064
+ // When coalescing spans, some physical pages which
1065
+ // were not returned to the OS previously because
1066
+ // they were only partially covered by the span suddenly
1067
+ // become available for scavenging. We want to make sure
1068
+ // those holes are filled in, and the span is properly
1069
+ // scavenged. Rather than trying to detect those holes
1070
+ // directly, we collect how many bytes were already
1071
+ // scavenged above and subtract that from heap_released
1072
+ // before re-scavenging the entire newly-coalesced span,
1073
+ // which will implicitly bump up heap_released.
1074
+ memstats .heap_released -= uint64 (prescavenged )
1075
+ s .scavenge ()
1076
+ }
1077
+
1078
+ // Insert s into the appropriate treap.
1079
+ if s .npreleased != 0 {
1080
+ h .scav .insert (s )
1081
+ } else {
1082
+ h .free .insert (s )
1083
+ }
1035
1084
}
1036
1085
1037
- func scavengeTreapNode (t * treapNode , now , limit uint64 ) uintptr {
1038
- s := t .spanKey
1039
- if (now - uint64 (s .unusedsince )) > limit && s .npreleased != s .npages {
1040
- if released := s .scavenge (); released != 0 {
1041
- return released
1086
+ // scavengeAll visits each node in the unscav treap and scavenges the
1087
+ // treapNode's span. It then removes the scavenged span from
1088
+ // unscav and adds it into scav before continuing. h must be locked.
1089
+ func (h * mheap ) scavengeAll (now , limit uint64 ) uintptr {
1090
+ // Compute the left-most child in unscav to start iteration from.
1091
+ t := h .free .treap
1092
+ if t == nil {
1093
+ return 0
1094
+ }
1095
+ for t .left != nil {
1096
+ t = t .left
1097
+ }
1098
+ // Iterate over the treap be computing t's successor before
1099
+ // potentially scavenging it.
1100
+ released := uintptr (0 )
1101
+ for t != nil {
1102
+ s := t .spanKey
1103
+ next := t .succ ()
1104
+ if (now - uint64 (s .unusedsince )) > limit {
1105
+ r := s .scavenge ()
1106
+ if r != 0 {
1107
+ // If we ended up scavenging s, then remove it from unscav
1108
+ // and add it to scav. This is safe to do since we've already
1109
+ // moved to t's successor.
1110
+ h .free .removeNode (t )
1111
+ h .scav .insert (s )
1112
+ released += r
1113
+ }
1042
1114
}
1115
+ // Move t forward to its successor to iterate over the whole
1116
+ // treap.
1117
+ t = next
1043
1118
}
1044
- return 0
1119
+ return released
1045
1120
}
1046
1121
1047
1122
func (h * mheap ) scavenge (k int32 , now , limit uint64 ) {
@@ -1051,13 +1126,13 @@ func (h *mheap) scavenge(k int32, now, limit uint64) {
1051
1126
gp := getg ()
1052
1127
gp .m .mallocing ++
1053
1128
lock (& h .lock )
1054
- sumreleased := scavengetreap ( h . free . treap , now , limit )
1129
+ released := h . scavengeAll ( now , limit )
1055
1130
unlock (& h .lock )
1056
1131
gp .m .mallocing --
1057
1132
1058
1133
if debug .gctrace > 0 {
1059
- if sumreleased > 0 {
1060
- print ("scvg" , k , ": " , sumreleased >> 20 , " MB released\n " )
1134
+ if released > 0 {
1135
+ print ("scvg" , k , ": " , released >> 20 , " MB released\n " )
1061
1136
}
1062
1137
print ("scvg" , k , ": inuse: " , memstats .heap_inuse >> 20 , ", idle: " , memstats .heap_idle >> 20 , ", sys: " , memstats .heap_sys >> 20 , ", released: " , memstats .heap_released >> 20 , ", consumed: " , (memstats .heap_sys - memstats .heap_released )>> 20 , " (MB)\n " )
1063
1138
}
0 commit comments