Skip to content

Commit 3c3be62

Browse files
committed
runtime: concurrent GC sweep
Moves sweep phase out of stoptheworld by adding background sweeper goroutine and lazy on-demand sweeping. It turned out to be somewhat trickier than I expected, because there is no point in time when we know size of live heap nor consistent number of mallocs and frees. So everything related to next_gc, mprof, memstats, etc becomes trickier. At the end of GC next_gc is conservatively set to heap_alloc*GOGC, which is much larger than real value. But after every sweep next_gc is decremented by freed*GOGC. So when everything is swept next_gc becomes what it should be. For mprof I had to introduce 3-generation scheme (allocs, revent_allocs, prev_allocs), because by the end of GC we know number of frees for the *previous* GC. Significant caution is required to not cross yet-unknown real value of next_gc. This is achieved by 2 means: 1. Whenever I allocate a span from MCentral, I sweep a span in that MCentral. 2. Whenever I allocate N pages from MHeap, I sweep until at least N pages are returned to heap. This provides quite strong guarantees that heap does not grow when it should now. http-1 allocated 7036 7033 -0.04% allocs 60 60 +0.00% cputime 51050 46700 -8.52% gc-pause-one 34060569 1777993 -94.78% gc-pause-total 2554 133 -94.79% latency-50 178448 170926 -4.22% latency-95 284350 198294 -30.26% latency-99 345191 220652 -36.08% rss 101564416 101007360 -0.55% sys-gc 6606832 6541296 -0.99% sys-heap 88801280 87752704 -1.18% sys-other 7334208 7405928 +0.98% sys-stack 524288 524288 +0.00% sys-total 103266608 102224216 -1.01% time 50339 46533 -7.56% virtual-mem 292990976 293728256 +0.25% garbage-1 allocated 2983818 2990889 +0.24% allocs 62880 62902 +0.03% cputime 16480000 16190000 -1.76% gc-pause-one 828462467 487875135 -41.11% gc-pause-total 4142312 2439375 -41.11% rss 1151709184 1153712128 +0.17% sys-gc 66068352 66068352 +0.00% sys-heap 1039728640 1039728640 +0.00% sys-other 37776064 40770176 +7.93% sys-stack 8781824 8781824 +0.00% sys-total 1152354880 1155348992 +0.26% time 16496998 16199876 -1.80% virtual-mem 1409564672 1402281984 -0.52% LGTM=rsc R=golang-codereviews, sameer, rsc, iant, jeremyjackins, gobot CC=golang-codereviews, khr https://golang.org/cl/46430043
1 parent 3b85f9b commit 3c3be62

File tree

6 files changed

+498
-126
lines changed

6 files changed

+498
-126
lines changed

src/pkg/runtime/malloc.goc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ runtime·free(void *v)
284284
if(raceenabled)
285285
runtime·racefree(v);
286286

287+
// Ensure that the span is swept.
288+
// If we free into an unswept span, we will corrupt GC bitmaps.
289+
runtime·MSpan_EnsureSwept(s);
290+
287291
if(s->specials != nil)
288292
runtime·freeallspecials(s, v, size);
289293

src/pkg/runtime/malloc.h

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,12 @@ struct MSpan
403403
PageID start; // starting page number
404404
uintptr npages; // number of pages in span
405405
MLink *freelist; // list of free objects
406+
// sweep generation:
407+
// if sweepgen == h->sweepgen - 2, the span needs sweeping
408+
// if sweepgen == h->sweepgen - 1, the span is currently being swept
409+
// if sweepgen == h->sweepgen, the span is swept and ready to use
410+
// h->sweepgen is incremented by 2 after every GC
411+
uint32 sweepgen;
406412
uint16 ref; // number of allocated objects in this span
407413
uint8 sizeclass; // size class
408414
uint8 state; // MSpanInUse etc
@@ -416,13 +422,16 @@ struct MSpan
416422
};
417423

418424
void runtime·MSpan_Init(MSpan *span, PageID start, uintptr npages);
425+
void runtime·MSpan_EnsureSwept(MSpan *span);
426+
bool runtime·MSpan_Sweep(MSpan *span);
419427

420428
// Every MSpan is in one doubly-linked list,
421429
// either one of the MHeap's free lists or one of the
422430
// MCentral's span lists. We use empty MSpan structures as list heads.
423431
void runtime·MSpanList_Init(MSpan *list);
424432
bool runtime·MSpanList_IsEmpty(MSpan *list);
425433
void runtime·MSpanList_Insert(MSpan *list, MSpan *span);
434+
void runtime·MSpanList_InsertBack(MSpan *list, MSpan *span);
426435
void runtime·MSpanList_Remove(MSpan *span); // from whatever list it is in
427436

428437

@@ -439,7 +448,7 @@ struct MCentral
439448
void runtime·MCentral_Init(MCentral *c, int32 sizeclass);
440449
int32 runtime·MCentral_AllocList(MCentral *c, MLink **first);
441450
void runtime·MCentral_FreeList(MCentral *c, MLink *first);
442-
void runtime·MCentral_FreeSpan(MCentral *c, MSpan *s, int32 n, MLink *start, MLink *end);
451+
bool runtime·MCentral_FreeSpan(MCentral *c, MSpan *s, int32 n, MLink *start, MLink *end);
443452

444453
// Main malloc heap.
445454
// The heap itself is the "free[]" and "large" arrays,
@@ -448,10 +457,15 @@ struct MHeap
448457
{
449458
Lock;
450459
MSpan free[MaxMHeapList]; // free lists of given length
451-
MSpan large; // free lists length >= MaxMHeapList
452-
MSpan **allspans;
460+
MSpan freelarge; // free lists length >= MaxMHeapList
461+
MSpan busy[MaxMHeapList]; // busy lists of large objects of given length
462+
MSpan busylarge; // busy lists of large objects length >= MaxMHeapList
463+
MSpan **allspans; // all spans out there
464+
MSpan **sweepspans; // copy of allspans referenced by sweeper
453465
uint32 nspan;
454466
uint32 nspancap;
467+
uint32 sweepgen; // sweep generation, see comment in MSpan
468+
uint32 sweepdone; // all spans are swept
455469

456470
// span lookup
457471
MSpan** spans;
@@ -487,7 +501,7 @@ struct MHeap
487501
extern MHeap runtime·mheap;
488502

489503
void runtime·MHeap_Init(MHeap *h);
490-
MSpan* runtime·MHeap_Alloc(MHeap *h, uintptr npage, int32 sizeclass, int32 acct, int32 zeroed);
504+
MSpan* runtime·MHeap_Alloc(MHeap *h, uintptr npage, int32 sizeclass, bool large, bool zeroed);
491505
void runtime·MHeap_Free(MHeap *h, MSpan *s, int32 acct);
492506
MSpan* runtime·MHeap_Lookup(MHeap *h, void *v);
493507
MSpan* runtime·MHeap_LookupMaybe(MHeap *h, void *v);
@@ -501,6 +515,7 @@ void* runtime·mallocgc(uintptr size, uintptr typ, uint32 flag);
501515
void* runtime·persistentalloc(uintptr size, uintptr align, uint64 *stat);
502516
int32 runtime·mlookup(void *v, byte **base, uintptr *size, MSpan **s);
503517
void runtime·gc(int32 force);
518+
uintptr runtime·sweepone(void);
504519
void runtime·markscan(void *v);
505520
void runtime·marknogc(void *v);
506521
void runtime·checkallocated(void *v, uintptr n);
@@ -528,7 +543,7 @@ enum
528543
};
529544

530545
void runtime·MProf_Malloc(void*, uintptr, uintptr);
531-
void runtime·MProf_Free(Bucket*, void*, uintptr);
546+
void runtime·MProf_Free(Bucket*, void*, uintptr, bool);
532547
void runtime·MProf_GC(void);
533548
void runtime·MProf_TraceGC(void);
534549
int32 runtime·gcprocs(void);
@@ -542,7 +557,7 @@ void runtime·removefinalizer(void*);
542557
void runtime·queuefinalizer(byte *p, FuncVal *fn, uintptr nret, Type *fint, PtrType *ot);
543558

544559
void runtime·freeallspecials(MSpan *span, void *p, uintptr size);
545-
bool runtime·freespecial(Special *s, void *p, uintptr size);
560+
bool runtime·freespecial(Special *s, void *p, uintptr size, bool freed);
546561

547562
enum
548563
{

src/pkg/runtime/mcentral.c

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,66 @@ runtime·MCentral_AllocList(MCentral *c, MLink **pfirst)
3939
{
4040
MSpan *s;
4141
int32 cap, n;
42+
uint32 sg;
4243

4344
runtime·lock(c);
44-
// Replenish central list if empty.
45-
if(runtime·MSpanList_IsEmpty(&c->nonempty)) {
46-
if(!MCentral_Grow(c)) {
45+
sg = runtime·mheap.sweepgen;
46+
retry:
47+
for(s = c->nonempty.next; s != &c->nonempty; s = s->next) {
48+
if(s->sweepgen == sg-2 && runtime·cas(&s->sweepgen, sg-2, sg-1)) {
4749
runtime·unlock(c);
48-
*pfirst = nil;
49-
return 0;
50+
runtime·MSpan_Sweep(s);
51+
runtime·lock(c);
52+
// the span could have been moved to heap, retry
53+
goto retry;
54+
}
55+
if(s->sweepgen == sg-1) {
56+
// the span is being swept by background sweeper, skip
57+
continue;
58+
}
59+
// we have a nonempty span that does not require sweeping, allocate from it
60+
goto havespan;
61+
}
62+
63+
for(s = c->empty.next; s != &c->empty; s = s->next) {
64+
if(s->sweepgen == sg-2 && runtime·cas(&s->sweepgen, sg-2, sg-1)) {
65+
// we have an empty span that requires sweeping,
66+
// sweep it and see if we can free some space in it
67+
runtime·MSpanList_Remove(s);
68+
// swept spans are at the end of the list
69+
runtime·MSpanList_InsertBack(&c->empty, s);
70+
runtime·unlock(c);
71+
runtime·MSpan_Sweep(s);
72+
runtime·lock(c);
73+
// the span could be moved to nonempty or heap, retry
74+
goto retry;
75+
}
76+
if(s->sweepgen == sg-1) {
77+
// the span is being swept by background sweeper, skip
78+
continue;
5079
}
80+
// already swept empty span,
81+
// all subsequent ones must also be either swept or in process of sweeping
82+
break;
83+
}
84+
85+
// Replenish central list if empty.
86+
if(!MCentral_Grow(c)) {
87+
runtime·unlock(c);
88+
*pfirst = nil;
89+
return 0;
5190
}
5291
s = c->nonempty.next;
92+
93+
havespan:
5394
cap = (s->npages << PageShift) / s->elemsize;
5495
n = cap - s->ref;
5596
*pfirst = s->freelist;
5697
s->freelist = nil;
5798
s->ref += n;
5899
c->nfree -= n;
59100
runtime·MSpanList_Remove(s);
60-
runtime·MSpanList_Insert(&c->empty, s);
101+
runtime·MSpanList_InsertBack(&c->empty, s);
61102
runtime·unlock(c);
62103
return n;
63104
}
@@ -116,8 +157,9 @@ MCentral_Free(MCentral *c, void *v)
116157
}
117158

118159
// Free n objects from a span s back into the central free list c.
119-
// Called from GC.
120-
void
160+
// Called during sweep.
161+
// Returns true if the span was returned to heap.
162+
bool
121163
runtime·MCentral_FreeSpan(MCentral *c, MSpan *s, int32 n, MLink *start, MLink *end)
122164
{
123165
int32 size;
@@ -136,19 +178,21 @@ runtime·MCentral_FreeSpan(MCentral *c, MSpan *s, int32 n, MLink *start, MLink *
136178
s->ref -= n;
137179
c->nfree += n;
138180

139-
// If s is completely freed, return it to the heap.
140-
if(s->ref == 0) {
141-
size = runtime·class_to_size[c->sizeclass];
142-
runtime·MSpanList_Remove(s);
143-
*(uintptr*)(s->start<<PageShift) = 1; // needs zeroing
144-
s->freelist = nil;
145-
c->nfree -= (s->npages << PageShift) / size;
146-
runtime·unlock(c);
147-
runtime·unmarkspan((byte*)(s->start<<PageShift), s->npages<<PageShift);
148-
runtime·MHeap_Free(&runtime·mheap, s, 0);
149-
} else {
181+
if(s->ref != 0) {
150182
runtime·unlock(c);
183+
return false;
151184
}
185+
186+
// s is completely freed, return it to the heap.
187+
size = runtime·class_to_size[c->sizeclass];
188+
runtime·MSpanList_Remove(s);
189+
*(uintptr*)(s->start<<PageShift) = 1; // needs zeroing
190+
s->freelist = nil;
191+
c->nfree -= (s->npages << PageShift) / size;
192+
runtime·unlock(c);
193+
runtime·unmarkspan((byte*)(s->start<<PageShift), s->npages<<PageShift);
194+
runtime·MHeap_Free(&runtime·mheap, s, 0);
195+
return true;
152196
}
153197

154198
void

0 commit comments

Comments
 (0)