Skip to content

Commit 56b5491

Browse files
committed
Merge remote-tracking branch 'origin/dev.garbage'
This commit moves the GC from free list allocation to bit mark allocation. Instead of using the bitmaps generated during the mark phases to generate free list and then using the free lists for allocation we allocate directly from the bitmaps. The change in the garbage benchmark name old time/op new time/op delta XBenchGarbage-12 2.22ms ± 1% 2.13ms ± 1% -3.90% (p=0.000 n=18+18) Change-Id: I17f57233336f0ca5ef5404c3be4ecb443ab622aa
2 parents d8d3351 + e9eaa18 commit 56b5491

15 files changed

+849
-427
lines changed

dev.garbage

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reviving dev.garbage branch for use in new garbage collection experiment.

src/runtime/cgocall.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ func cgoCheckUnknownPointer(p unsafe.Pointer, msg string) (base, i uintptr) {
529529
return
530530
}
531531

532-
b, hbits, span := heapBitsForObject(uintptr(p), 0, 0)
532+
b, hbits, span, _ := heapBitsForObject(uintptr(p), 0, 0)
533533
base = b
534534
if base == 0 {
535535
return

src/runtime/heapdump.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ func dumproots() {
447447
continue
448448
}
449449
spf := (*specialfinalizer)(unsafe.Pointer(sp))
450-
p := unsafe.Pointer((uintptr(s.start) << _PageShift) + uintptr(spf.special.offset))
450+
p := unsafe.Pointer(s.base() + uintptr(spf.special.offset))
451451
dumpfinalizer(p, spf.fn, spf.fint, spf.ot)
452452
}
453453
}
@@ -467,15 +467,19 @@ func dumpobjs() {
467467
if s.state != _MSpanInUse {
468468
continue
469469
}
470-
p := uintptr(s.start << _PageShift)
470+
p := s.base()
471471
size := s.elemsize
472472
n := (s.npages << _PageShift) / size
473473
if n > uintptr(len(freemark)) {
474474
throw("freemark array doesn't have enough entries")
475475
}
476-
for l := s.freelist; l.ptr() != nil; l = l.ptr().next {
477-
freemark[(uintptr(l)-p)/size] = true
476+
477+
for freeIndex := s.freeindex; freeIndex < s.nelems; freeIndex++ {
478+
if s.isFree(freeIndex) {
479+
freemark[freeIndex] = true
480+
}
478481
}
482+
479483
for j := uintptr(0); j < n; j, p = j+1, p+size {
480484
if freemark[j] {
481485
freemark[j] = false
@@ -615,7 +619,7 @@ func dumpmemprof() {
615619
continue
616620
}
617621
spp := (*specialprofile)(unsafe.Pointer(sp))
618-
p := uintptr(s.start<<_PageShift) + uintptr(spp.special.offset)
622+
p := s.base() + uintptr(spp.special.offset)
619623
dumpint(tagAllocSample)
620624
dumpint(uint64(p))
621625
dumpint(uint64(uintptr(unsafe.Pointer(spp.b))))
@@ -710,7 +714,7 @@ func makeheapobjbv(p uintptr, size uintptr) bitvector {
710714
i := uintptr(0)
711715
hbits := heapBitsForAddr(p)
712716
for ; i < nptr; i++ {
713-
if i >= 2 && !hbits.isMarked() {
717+
if i >= 2 && !hbits.morePointers() {
714718
break // end of object
715719
}
716720
if hbits.isPointer() {

src/runtime/malloc.go

Lines changed: 83 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ const (
9494
pageShift = _PageShift
9595
pageSize = _PageSize
9696
pageMask = _PageMask
97+
// By construction, single page spans of the smallest object class
98+
// have the most objects per span.
99+
maxObjsPerSpan = pageSize / 8
97100

98101
mSpanInUse = _MSpanInUse
99102

@@ -167,9 +170,6 @@ const (
167170
_MaxGcproc = 32
168171
)
169172

170-
// Page number (address>>pageShift)
171-
type pageID uintptr
172-
173173
const _MaxArena32 = 2 << 30
174174

175175
// OS-defined helpers:
@@ -384,6 +384,10 @@ func sysReserveHigh(n uintptr, reserved *bool) unsafe.Pointer {
384384
return sysReserve(nil, n, reserved)
385385
}
386386

387+
// sysAlloc allocates the next n bytes from the heap arena. The
388+
// returned pointer is always _PageSize aligned and between
389+
// h.arena_start and h.arena_end. sysAlloc returns nil on failure.
390+
// There is no corresponding free function.
387391
func (h *mheap) sysAlloc(n uintptr) unsafe.Pointer {
388392
if n > h.arena_end-h.arena_used {
389393
// We are in 32-bit mode, maybe we didn't use all possible address space yet.
@@ -484,6 +488,65 @@ func (h *mheap) sysAlloc(n uintptr) unsafe.Pointer {
484488
// base address for all 0-byte allocations
485489
var zerobase uintptr
486490

491+
// nextFreeFast returns the next free object if one is quickly available.
492+
// Otherwise it returns 0.
493+
func nextFreeFast(s *mspan) gclinkptr {
494+
theBit := sys.Ctz64(s.allocCache) // Is there a free object in the allocCache?
495+
if theBit < 64 {
496+
result := s.freeindex + uintptr(theBit)
497+
if result < s.nelems {
498+
freeidx := result + 1
499+
if freeidx%64 == 0 && freeidx != s.nelems {
500+
return 0
501+
}
502+
s.allocCache >>= (theBit + 1)
503+
s.freeindex = freeidx
504+
v := gclinkptr(result*s.elemsize + s.base())
505+
s.allocCount++
506+
return v
507+
}
508+
}
509+
return 0
510+
}
511+
512+
// nextFree returns the next free object from the cached span if one is available.
513+
// Otherwise it refills the cache with a span with an available object and
514+
// returns that object along with a flag indicating that this was a heavy
515+
// weight allocation. If it is a heavy weight allocation the caller must
516+
// determine whether a new GC cycle needs to be started or if the GC is active
517+
// whether this goroutine needs to assist the GC.
518+
func (c *mcache) nextFree(sizeclass int8) (v gclinkptr, s *mspan, shouldhelpgc bool) {
519+
s = c.alloc[sizeclass]
520+
shouldhelpgc = false
521+
freeIndex := s.nextFreeIndex()
522+
if freeIndex == s.nelems {
523+
// The span is full.
524+
if uintptr(s.allocCount) != s.nelems {
525+
println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
526+
throw("s.allocCount != s.nelems && freeIndex == s.nelems")
527+
}
528+
systemstack(func() {
529+
c.refill(int32(sizeclass))
530+
})
531+
shouldhelpgc = true
532+
s = c.alloc[sizeclass]
533+
534+
freeIndex = s.nextFreeIndex()
535+
}
536+
537+
if freeIndex >= s.nelems {
538+
throw("freeIndex is not valid")
539+
}
540+
541+
v = gclinkptr(freeIndex*s.elemsize + s.base())
542+
s.allocCount++
543+
if uintptr(s.allocCount) > s.nelems {
544+
println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
545+
throw("s.allocCount > s.nelems")
546+
}
547+
return
548+
}
549+
487550
// Allocate an object of size bytes.
488551
// Small objects are allocated from the per-P cache's free lists.
489552
// Large objects (> 32 kB) are allocated straight from the heap.
@@ -538,7 +601,6 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
538601
shouldhelpgc := false
539602
dataSize := size
540603
c := gomcache()
541-
var s *mspan
542604
var x unsafe.Pointer
543605
noscan := typ == nil || typ.kind&kindNoPointers != 0
544606
if size <= maxSmallSize {
@@ -591,20 +653,11 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
591653
return x
592654
}
593655
// Allocate a new maxTinySize block.
594-
s = c.alloc[tinySizeClass]
595-
v := s.freelist
596-
if v.ptr() == nil {
597-
systemstack(func() {
598-
c.refill(tinySizeClass)
599-
})
600-
shouldhelpgc = true
601-
s = c.alloc[tinySizeClass]
602-
v = s.freelist
656+
span := c.alloc[tinySizeClass]
657+
v := nextFreeFast(span)
658+
if v == 0 {
659+
v, _, shouldhelpgc = c.nextFree(tinySizeClass)
603660
}
604-
s.freelist = v.ptr().next
605-
s.ref++
606-
// prefetchnta offers best performance, see change list message.
607-
prefetchnta(uintptr(v.ptr().next))
608661
x = unsafe.Pointer(v)
609662
(*[2]uint64)(x)[0] = 0
610663
(*[2]uint64)(x)[1] = 0
@@ -623,26 +676,14 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
623676
sizeclass = size_to_class128[(size-1024+127)>>7]
624677
}
625678
size = uintptr(class_to_size[sizeclass])
626-
s = c.alloc[sizeclass]
627-
v := s.freelist
628-
if v.ptr() == nil {
629-
systemstack(func() {
630-
c.refill(int32(sizeclass))
631-
})
632-
shouldhelpgc = true
633-
s = c.alloc[sizeclass]
634-
v = s.freelist
679+
span := c.alloc[sizeclass]
680+
v := nextFreeFast(span)
681+
if v == 0 {
682+
v, span, shouldhelpgc = c.nextFree(sizeclass)
635683
}
636-
s.freelist = v.ptr().next
637-
s.ref++
638-
// prefetchnta offers best performance, see change list message.
639-
prefetchnta(uintptr(v.ptr().next))
640684
x = unsafe.Pointer(v)
641-
if needzero {
642-
v.ptr().next = 0
643-
if size > 2*sys.PtrSize && ((*[2]uintptr)(x))[1] != 0 {
644-
memclr(unsafe.Pointer(v), size)
645-
}
685+
if needzero && span.needzero != 0 {
686+
memclr(unsafe.Pointer(v), size)
646687
}
647688
}
648689
} else {
@@ -651,13 +692,15 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
651692
systemstack(func() {
652693
s = largeAlloc(size, needzero)
653694
})
654-
x = unsafe.Pointer(uintptr(s.start << pageShift))
695+
s.freeindex = 1
696+
s.allocCount = 1
697+
x = unsafe.Pointer(s.base())
655698
size = s.elemsize
656699
}
657700

658701
var scanSize uintptr
659702
if noscan {
660-
// All objects are pre-marked as noscan. Nothing to do.
703+
heapBitsSetTypeNoScan(uintptr(x), size)
661704
} else {
662705
// If allocating a defer+arg block, now that we've picked a malloc size
663706
// large enough to hold everything, cut the "asked for" size down to
@@ -701,6 +744,7 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
701744
if raceenabled {
702745
racemalloc(x, size)
703746
}
747+
704748
if msanenabled {
705749
msanmalloc(x, size)
706750
}
@@ -755,8 +799,8 @@ func largeAlloc(size uintptr, needzero bool) *mspan {
755799
if s == nil {
756800
throw("out of memory")
757801
}
758-
s.limit = uintptr(s.start)<<_PageShift + size
759-
heapBitsForSpan(s.base()).initSpan(s.layout())
802+
s.limit = s.base() + size
803+
heapBitsForSpan(s.base()).initSpan(s)
760804
return s
761805
}
762806

0 commit comments

Comments
 (0)