返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

3.4.3 辅助

发布于 2024-10-12 19:16:04 字数 4615 浏览 0 评论 0 收藏 0

某些时候,G 逻辑会快速分配大量内存,这可能导致分配和回收失衡。

为抑制这种行为,每个 G 被赋予一定信用额度(gcAssistBytes)。

在回收期间,每次分配都消费同等大小额度。

如额度为负,则必须参加辅助回收,偿还债务,挣取后续分配可用额度。

// runtime2.go

type g struct {
    gcAssistBytes int64
}
// malloc.go

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    
    // 仅在垃圾回收期间启用该机制。
    if gcBlackenEnabled != 0 {
         assistG = getg()
        
        // 减去本次消费额度。
        assistG.gcAssistBytes -= int64(size)
        
        // 如果负债,则参与辅助回收。
        if assistG.gcAssistBytes < 0 {
            gcAssistAlloc(assistG)
        }
    }
}

gcAssistAlloc

欠债的 G,没有立即参与辅助回收,而是先尝试从全局账户(gcController.bgScanCredit)偷窃还债。

工作量和债务计算中带入 gcController.assistBytesPerWork,有一定的工作和分配比率浮动。

// mgcmark.go

func gcAssistAlloc(gp *g) {
    
retry:
    
    // 计算偿还债务所需的工作量。
    debtBytes := -gp.gcAssistBytes
    scanWork := int64(gcController.assistWorkPerByte * float64(debtBytes))
    
    // 如工作量过低,会尝试多做一些(64KB),以便为后续消费提供足够额度。
    if scanWork < gcOverAssistWork {
        scanWork = gcOverAssistWork
        debtBytes = int64(gcController.assistBytesPerWork * float64(scanWork))
    }
    
    // 尝试从公共账户直接偷窃。
    bgScanCredit := atomic.Loadint64(&gcController.bgScanCredit)
    stolen := int64(0)
    if bgScanCredit > 0 {
        
        // 按工作量(最小 64KB,大于实际债务)从公共账户偷窃。
        if bgScanCredit < scanWork {
            stolen = bgScanCredit
            gp.gcAssistBytes += 1 + int64(gcController.assistBytesPerWork*float64(stolen))
        } else {
            stolen = scanWork
            gp.gcAssistBytes += debtBytes
        }
        
        atomic.Xaddint64(&gcController.bgScanCredit, -stolen)
        scanWork -= stolen
        
        // 如剩余工作量为 0,那么表示债务偿清,还有多余额度。
        // 无需参与辅助工作,返回 mallocgc 继续。
        if scanWork == 0 {
            return
        }
    }
    
    // 开始辅助回收。
    systemstack(func() {
        gcAssistAlloc1(gp, scanWork)
    })
    
    // 依旧未能偿还债务,
    if gp.gcAssistBytes < 0 {
        
        // 抢占调度,稍后继续挣钱。
        if gp.preempt {
            Gosched()
            goto retry
        }
        
        // 放入专门队列(work.assistQueue),休眠。
        // 直到全局账号有钱时被唤醒。
        if !gcParkAssist() {
            goto retry
        }
    }
}
// mgcmark.go

func gcAssistAlloc1(gp *g, scanWork int64) {
    
    if atomic.Load(&gcBlackenEnabled) == 0 {
        gp.gcAssistBytes = 0
        return
    }
    
    // 执行标记任务。
    gcw := &getg().m.p.ptr().gcw
    workDone := gcDrainN(gcw, scanWork)        // gcDrainN 指定工作量,可被抢占。
    
    // 将劳动所得存入个人信用账户。
    gp.gcAssistBytes += 1 + int64(gcController.assistBytesPerWork * float64(workDone))
}

gcFlushBgCredit

在 gcDrain 里,工人会将劳动所得存入公共账号(gcController.bgScanCredit)。

为欠债休眠的 G 偿还债务,唤醒它们继续工作。

// mgcmark.go

func gcDrain(gcw *gcWork, flags gcDrainFlags) {
    
    // Flush background scan work credit to the global
    // account if we've accumulated enough locally so
    // mutator assists can draw on it.
    if gcw.scanWork >= gcCreditSlack {
        if flushBgCredit {
            gcFlushBgCredit(gcw.scanWork - initScanWork)
        }
    }
    
done:
    
    // Flush remaining scan work credit.
    if gcw.scanWork > 0 {
        if flushBgCredit {
            gcFlushBgCredit(gcw.scanWork - initScanWork)
        }
    }
}
func gcFlushBgCredit(scanWork int64) {
    
    // 如队列中没有辅助 G 休眠,存款后退出。
    if work.assistQueue.q.empty() {
        atomic.Xaddint64(&gcController.bgScanCredit, scanWork)
        return
    }
    
    scanBytes := int64(float64(scanWork) * gcController.assistBytesPerWork)
    
    // 遍历休眠队列。
    for !work.assistQueue.q.empty() && scanBytes > 0 {
        gp := work.assistQueue.q.pop()
        
        // 如能替它偿还全部债务。
        if scanBytes + gp.gcAssistBytes >= 0 {
            
            // G 额度是负,直接减去,为其偿还全部债务。
            scanBytes += gp.gcAssistBytes
            gp.gcAssistBytes = 0
            
            // 唤醒,让它去继续工作。
            xgp := gp
            ready(gp, 0, false)
            
        } else {
            
            // 偿还部分债务。
            gp.gcAssistBytes += scanBytes
            scanBytes = 0
            
            // 依旧负债,继续呆在休眠队列。
            work.assistQueue.q.pushBack(gp)
            
            // 余款已空,终止还债循环。
            break
        }
    }
    
    // 将余额存入公共账户。
    if scanBytes > 0 {
        scanWork = int64(float64(scanBytes) * gcController.assistWorkPerByte)
        atomic.Xaddint64(&gcController.bgScanCredit, scanWork)
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文