静态语言的运行时库

举报
码乐 发表于 2024/03/21 08:42:42 2024/03/21
【摘要】 我们知道作为强静态类型语言的go没有虚拟机,而是靠运行时,它在go开发中使用频率比较高,单独整理以下内容。 1 垃圾回收 runtime GCGo将内存对象分为以下几类:微小对象 Tiny size <16B 使用mcache的微小分配器分配大小小于 16个字节的对象,在单个16字节块上可以完成多个微小分配小对象 16B ~32KB 大小在16个字节和32k字节之间的对象被分配在G运行所...

我们知道作为强静态类型语言的go没有虚拟机,而是靠运行时,它在go开发中使用频率比较高,单独整理以下内容。

1 垃圾回收 runtime GC

Go将内存对象分为以下几类:

  • 微小对象 Tiny size <16B

      使用mcache的微小分配器分配大小小于 16个字节的对象,在单个16字节块上可以完成多个微小分配
    
  • 小对象 16B ~32KB

      大小在16个字节和32k字节之间的对象被分配在G运行所在的P ncache对应 mspan size class
    

在微小型和小型 对象种秒如果mspan列表为空,分配器将从 mheap获取大量页面用于mspan
如果mheap为空或 没有足够大的页面满足分配请求。

那么它将从操作系统分配一组新的页 至少 1MB。

  • 大对象

     大小 >32KB, 大于 32KB 的对象直接分配在 mheap的相应大小类上 size class
    

如果 mheap为空或 没有足够大的页面满足分配请求,则它将从操作系统分配一组新的页 至少 1MB

GC与线程同时运行,类型准确(又称精确),允许多个GC线程并行运行。它是一个使用写屏障的并发标记和扫除程序。

它是非生成性和非压缩性的。分配是使用每P分配的大小隔离来完成不同区域,以尽量减少碎片,同时消除常见情况下的锁。

该算法可分解为几个步骤。
这是对正在使用的算法的一个高级描述。对于GC的概述,一个好的开始可以是Richard Jones的gchandbook.org。 查看本文最后。

    1. GC执行扫描终止。

      a. 停止执行。这将导致所有Ps达到GC的安全点。

      b. 清理任何未扫除的对象。只有在以下情况下才会出现未清理的对象, 这个GC循环在预期时间之前被强制执行。

    1. GC执行标记阶段。

      a. 为标记阶段做准备,将gcphase设置为_GCmark(从_GCoff),启用写屏障,启用mutator辅助,并排队等待根标记作业。

      在所有的Ps都启用写屏障之前,不可以对任何对象进行在所有的Ps都启用了写屏障之前,不得对任何对象进行扫描,这是用STW完成的。

      b. 启动执行。从此时起,GC工作由调度器启动的标记工完成,并由作为分配的一部分进行的辅助工作完成。

      写入屏障会遮蔽覆盖的指针和新的指针值。写入(详见mbarrier.go)。
      新分配的对象立即被标记为黑色。

      c. GC执行根标记工作。这包括扫描所有栈,对所有球体进行标记,并对堆外数据结构中的任何堆指针进行标记。

      堆外运行时数据结构中的任何堆指针。扫描一个堆栈会停止一个进程,对其堆栈中发现的任何指针进行着色,然后恢复该程序。

      d. GC耗尽工作队列中的灰色对象,将每个灰色对象的工作队列,将每个灰色对象扫描为黑色,并对该对象中发现的所有指针进行阴影处理。
      (反过来可能会将这些指针添加到工作队列中)。

      e. 因为GC的工作分散在本地缓存中,所以GC使用一种分布式终止算法来检测何时没有更多的根标记工作或灰色对象(见gcMarkDone)。

      这时,GC过渡到标记终止。

  • 3.GC执行标记终止。

    a. 停止执行。

    b. 将gcphase设置为_GCmarktermination,并禁用worker和助手。

    c. 进行内务管理,如刷新缓存。

  • 4.GC执行扫除阶段。

     a. 通过将gcphase设置为_GCoff,为扫除阶段做准备。
                a. 通过将gcphase设置为_GCoff,为清扫阶段做准备,设置清扫状态并禁用写屏障。
    
                b. 启动执行。从这时起,新分配的对象是白色的,必要时在使用前分配扫频对象。
    
     c. GC在后台进行并发的清扫,并响应响应分配。见下面的描述。
    
  • 5.当有足够的分配发生时,重放上面的序列从上面的1开始。参见下面关于GC执行频率的讨论。

并行的清理。

扫除阶段与正常的程序执行同时进行。堆被逐个对象地扫过(当一个goroutine需要另一个对象时)。

这将与在后台goroutine中同时进行(这有助于不受CPU约束的程序)。在STW标记终止时,所有span对象都被标记为 “需要清理”。

背景扫频的goroutine简单地逐一执行扫描。

为了避免在有未扫的对象时请求更多的操作系统内存,当一个时,它首先会试图通过清扫来获取更多的内存。

通过清扫,当一个goroutine需要分配一个新的小对象对象时,它会扫除相同对象大小的小对象对象,直到它至少释放了一个对象。

当一个goroutine需要从堆中分配大对象对象时。它就会扫描对象,直到它至少释放了那么多页到堆中。

有一种情况,这可能是不够的:如果一个goroutine清扫并释放了两个不相邻的一页对象到堆中,它将分配一个新的两页的。

但仍有其他未扫除的单页对象可以被合并为一个双页范围。合并成一个两页的对象范围。

确保不对未扫除的跨页进行任何操作是非常重要的(这将破坏GC位图中的标记位)。

在GC过程中,所有的缓存都被刷入中央缓存。所以它们是空的。当一个goroutine在mcache中抓取一个新的对象时,它就会对其进行扫描。

当一个goroutine明确释放一个对象或者设置一个finalizer时,它将确保该对象被扫除(要么扫除,要么等待并发的扫除结束)。

只有当所有对象都被扫过之后,finalizer goroutine才会被启动。

当下一次GC开始时,它会清扫所有尚未清扫的对象(如果有的话)。

  • GC执行频率。

下一次GC是在我们分配了一个额外的内存量之后,这个内存量与已经在使用的数量。这个比例是由GOGC环境变量控制的(默认为100)。

如果GOGC=100,并且我们正在使用4M,那么当我们达到8M时,我们将再次分配GC。
(这个标记是由gcController.heapGoal方法计算的)。

这使GC成本与分配成本保持在与分配成本成线性比例。调整GOGC只是改变了这个线性常数(也包括使用的额外内存量)。

为了防止在扫描大型对象时出现长时间的停顿,并提高并行性,垃圾收集器将扫描对象的工作分割开来。

大于maxObletBytes的对象的扫描工作分成 “blets”,最多不超过maxObletBytes。

当扫描遇到一个大的时,它只扫描第一个blet,并将其余的blet排队作为新的扫描任务。
oblets作为新的扫描任务。

调用后运行一次垃圾回收,并阻止调用者,直到垃圾收集完成。它也可能阻塞整个程序。

我们认为一个周期是:扫描终止、标记、标记 终止,以及扫描。
这个函数不应该返回, 直到一个完整的周期完成,从开始到结束全部完成。
因此,我们总是希望完成当前的周期并开始一个新的周期。

        1.在扫频终止、标记、或标记终止的周期中N时,等待标记终止N的完成,并转入到扫频N。

        2.在扫频N中,帮助扫频N。这时我们可以开始一个完整的循环N+1。

        3.通过启动扫频终止N+1来触发N+1循环。

        4.等待标记终止N+1完成。

        5.帮助扫频N+1,直到它完成。

这一切都要标记出来,以应对GC可能会自己向前推进。例如,当我们封锁直到标记终止N,我们可能会在N+2周期中被唤醒。

等到当前的清扫终止、标记、以及标记终止完成。

2 竞争检测 runtime race

文件只是确保我们在竞争构建中链接到 runtime/cgo。 这又确保了运行时使用pthread_create来创建线程。

预建的竞争运行时位于race_GOOS_GOARCH.syso中。 对运行时的调用是直接从 src/runtime/race.go 中进行的。

在进化的系统中,我们总是使用系统DLLs来创建线程。

所以我们使用 race_darwin_$GOARCH.go 来提供源自 syso 的 符号信息,而不需要调用cgo。

这使得-race可以在没有C工具链的Mac系统上使用。

实现数据竞争检测逻辑。 没有提供公共接口。 关于竞争检测器的细节,见:

	https://golang.org/doc/articles/race_detector.html

2 运行时调试 runtime debug

软件包debug包含了程序在运行时进行自我调试的功能。 它们正在运行。

  • 函数 Stack()

Stack返回调用它的goroutine的格式化的堆栈跟踪。
它调用runtime.Stack,有足够大的缓冲区来捕捉整个跟踪。

  • 函数 ReadGCStats()

func ReadGCStats(stats *GCStats)

ReadGCStats将有关垃圾收集的统计数据读入统计信息。 暂停历史的条目数与系统有关。
stats.Pause slice如果足够大将被重复使用,否则将被重新分配。

ReadGCStats 可以使用 stats.Pause slice 的全部容量。

如果stats.PauseQuantiles非空,ReadGCStats将用量值填充它总结暂停时间的分布。

例如,如果 len(stats.PauseQuantiles)是5,它将被填入最小。
25%、50%、75%和最大停顿时间。

3 运行时原子操作 runtime internal atomic

runtime internal atomic 提供原子操作,独立于sync/atomic。
向运行时提供原子操作。

在大多数平台上,编译器都知道这个包中定义的函数中定义的函数,它们被替换成特定平台的本征。
在其他平台上,通用的实现是可用的。

除非另有说明,本包中定义的操作在顺序上是在它们所操作的值方面,在不同的线程之间是一致的。

更具体地说具体来说,在一个线程上以特定顺序发生的操作。

将总是被另一个线程观察到以完全相同的顺序发生。

3 数学溢出检测 runtime internal math

在支持的平台上,这是一个由编译器降低的内在因素。

MulUintptr(a, b uintptr) (uintptr, bool) 
MulUintptr返回a * b以及乘法是否溢出。

乘积位的上半部分在hi中返回,下半部分在lo中返回。

Mul64(x, y uint64) (hi, lo uint64)
Mul64返回x和y的128位乘积:(hi, lo)= x * y

上半部分返回到lo中。

这是从math/bits.Mul64复制过来的。

在支持的平台上,这是由编译器降低的一个内在因素。

3 帮助函数 runtime internal startlinetest

包含 runtime_test.TestStartLineAsm 的帮助程序。

4 系统运行时 runtime internal sys

包含系统、配置和架构特定的运行时使用的常量。

    type NotInHeap struct{ _ nih }

NotInHeap是指一个类型决不能从GC’d堆或堆栈上分配。并且被称为不在堆中。

1 其他类型可以嵌入NotInHeap以使其不在堆中。

具体来说,指向这些类型的指针指向这些类型的指针必须总是不能通过runtime.inheap检查。

该类型可以被用于用于全局变量,或用于非托管内存中的对象(例如,用
sysAlloc, persistentalloc, rfixalloc, 或从人工管理的对象中分配)。

new(T), make([]T), append([]T, ...)

隐式堆T的//分配是不允许的。(尽管隐式分配在
隐式分配在运行时也是不允许的)。

  1. 常规类型的指针

(除 "unsafe.Pointer "外)不能被转换为普通类型的指针。
转换成一个非堆内类型的指针,即使它们的底层类型相同,相同的底层类型。

3.任何包含非堆内类型的类型本身被认为是非堆内类型。

- 如果结构和数组的元素不在堆内,那么它们就是不在堆内。
- 不允许地图和通道包含非堆内类型。

4.可以省略指向非堆内类型的指针的写入障碍。

最后一点才是NotInHeap的真正好处。
运行时使用它用于低级别的内部结构,以避免在调度器和内存分配器中出现内存障碍。

因为它们是非法的,或者仅仅是低效率的地方。这种机制是相当安全的,并且不会影响到运行时的可读性。

5 系统调用包装 runtime internal syscall

这是一个推送链接名,将Syscall6导出为
syscall.RawSyscall6。

go:uintptrkeepalive,因为uintptr参数可能是转换后的指针。

因为uintptr参数可能是转换后的指针,需要在调用者中保持活力(这对Syscall6是隐含的,因为它没有主体)。

go:nosplit 因为堆栈复制没有考虑uintptrkeepalive,所以堆栈必须不增长。堆栈复制不能盲目地假设所有uintptr 参数都是指针,因为有些值可能看起来像指针。

但并不是真正的指针,而调整它们的值会破坏调用。

这是一个独立的包装器,因为我们不能把一个函数导出为两个名称。汇编实现自己的名字Syscall6将不会被受到链接名的影响。

6 运行时执行跟踪 runtime trace

trace包含了为程序生成痕迹的设施为执行跟踪器生成跟踪。

  • 追踪运行时的活动

执行跟踪可以捕捉到各种各样的执行事件,如 goroutine创建/阻塞/解阻塞,系统调用进入/退出/阻塞。

与GC有关的事件、堆大小的变化、处理器的启动/停止等。 当CPU分析被激活时,执行追踪器会努力将这些样本也包括在内。

也包括这些样本。 当CPU剖析激活时,执行跟踪器也会努力包括这些样本。
大多数事件都会被捕获。生成的跟踪可以被解释为 使用 go tool trace 。

go test 支持跟踪用标准测试包构建的测试和基准。
go test 中内置了对测试和基准的跟踪。

例如,下面的 命令在当前目录下运行测试,并写入跟踪 文件(trace.out)。

    go test -trace=trace.out

这个运行时/跟踪包提供了一些API,以便在一个独立的程序中添加等效的跟踪功能。

还有一个标准的HTTP接口来追踪数据。添加以下一行将在/debug/pprof/trace URL下安装一个处理程序来下载一个实时跟踪。

    import _ "net/http/pprof"

请参阅 net/http/pprof 软件包,以了解更多关于这个导入的所有调试端点的详细信息。

用户标注包trace提供了用户注解API,可以用来 在执行过程中记录有趣的事件。

有三种类型的用户注解:日志信息、区域。 和任务。

日志会向执行跟踪系统发送一条有时间戳的消息,同时还有 额外的信息,如消息的类别和 哪个goroutine调用了Log。

执行追踪器提供的用户界面可以利用日志类别和消息来过滤 和分组goroutine,使用Log的类别和提供的消息。

在日志中的一个区域是用来记录一个goroutine执行过程中的时间间隔的。
根据定义,一个区域在同一个goroutine中开始和结束。

区域可以被嵌套以表示子间隔。
例如,下面的代码记录了执行中的四个区域 追踪卡布奇诺制作过程中连续步骤的持续时间操作。

	trace.WithRegion(ctx, "makeCappuccino", func() {
    
       orderID allows to identify a specific order
       among many cappuccino order region records.
   trace.Log(ctx, "orderID", orderID)

   trace.WithRegion(ctx, "steamMilk", steamMilk)
   trace.WithRegion(ctx, "extractCoffee", extractCoffee)
   trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee)
})

任务是一个更高层次的组件,它有助于追踪逻辑上的诸如RPC请求、HTTP请求或一个可能需要多个goroutines的有趣的本地操作。

可能需要多个Goroutines一起工作的有趣的本地操作一起工作。

由于任务可能涉及多个goroutines。
它们是通过context.Context对象来追踪的。NewTask创建一个新的任务并将其嵌入返回的context.Context对象中。

如果有的话,日志信息和区域被附加到任务中,在传递给 Log 和 WithRegion 的 Context。

例如,假设我们决定将牛奶打成泡沫,提取咖啡。并在不同的goroutine中混合牛奶和咖啡。通过一个任务。

追踪工具可以确定在一个特定的卡布奇诺的订单。

	ctx, task := trace.NewTask(ctx, "makeCappuccino")
	    trace.Log(ctx, "orderID", orderID)
    
	    milk := make(chan bool)
	    espresso := make(chan bool)
    
	    go func() {
	            trace.WithRegion(ctx, "steamMilk", steamMilk)
	            milk <- true
	    }()
	    go func() {
	            trace.WithRegion(ctx, "extractCoffee", extractCoffee)
	            espresso <- true
	    }()
	    go func() {
	            defer task.End()     When assemble is done, the order is complete.
	            <-espresso
	            <-milk
	            trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee)
	    }()

跟踪工具通过测量任务创建和任务结束之间的时间来计算任务的延迟。

任务创建和任务结束之间的时间,并提供追踪中发现的每种任务类型的延迟分布。

7 内存分配器统计信息 runtime ReadMemStats

返回的内存分配器统计信息是在调用ReadMemStats时最新的。

调用ReadMemStats。这是与堆统计不同的。

它是最近完成的垃圾收集周期的一个快照。收集周期的快照。

8 运行时指标 runtime metrics

metrics 提供了一个稳定的接口来访问实现定义的度量的稳定接口。
这个包类似于现有的函数如runtime.ReadMemStats和debug.ReadGCStats,但明显更通用。

这个包所定义的指标集可以随着运行时本身的发展而变化变化,也使得不同的Go实现有不同的变化,其相关的度量集可能不会相交。

  • 接口

由一个字符串键指定,而不是例如结构中的字段名。
结构中的字段名。支持的度量的完整列表总是可以在描述中。

每个描述还包括关于指标的有用信息,关于指标的有用信息。

因此,我们鼓励该API的用户对由All返回的片段定义的指标,以保持跨Go版本的兼容性。
当然,在一些情况下,读取特定的度量是非常重要的。

对于这些情况,我们鼓励用户使用构建标签,尽管指标可能会被弃用和删除。

尽管指标可能会被废弃和删除,但用户应该认为这是一个特殊的、罕见的事件,与特定Go实现的巨大变化相吻合。

恰好是在某个特定的Go实现中发生了非常大的变化。

每个指标衡键都有一个 “种类”,描述度量衡值的格式。

为了不影响这个包的用户,一个给定的度量值的 "种类 "保证不会改变。

的 "种类 "被保证不会改变。如果它必须改变,那么一个新的度量将被引入用一个新的键和一个新的 “种类”。

  • Metric key format

如前所述,指标 Metric key 是字符串。它们的格式简单且定义明确。

旨在使人和机器都能读懂。它被分成两个部分。

用冒号隔开:一个有根的路径和一个单位。选择将单位包含在钥匙的选择是出于兼容性的考虑:如果一个度量衡的单位发生了变化,它的语义很可能也会发生变化。

也发生了变化,所以应该引入一个新的键。关于Metric key 的路径和单位格式的精确定义的更多细节,见描述结构的名称字段的文档。

  • 关于浮点数支持的说明

这个包支持其值为浮点表示的度量。为了提高易用性为了提高使用的方便性,本包承诺不产生以下的类的浮点值。NaN,无穷大。

  • 支持的指标

下面是支持的指标的完整列表,按字母顺序排列。

/cgo/go-to-c-calls:calls

    当前进程从Go到C的调用数。

/cpu/classes/gc/mark/assist:cpu-seconds

    估计执行GC任务的goroutines的总CPU时间以协助GC并防止其落后于应用程序。
    这个指标是一个高估,并不能直接与系统CPU时间的测量。只能与其他/cpu/classes度量。

/cpu/classes/gc/mark/dedicated:cpu-seconds

    在专门用于GC任务的处理器(由GOMAXPROCS定义)上执行GC任务所花费的CPU总时间估计。
    处理器(由GOMAXPROCS定义)专门用于这些任务。
    
    这包括因GC而停止的世界所花费的时间。
    
    这个指标是一个高估,并不能直接与系统CPU时间的测量。
    
    只能与其他/cpu/classes度量。

/cpu/classes/gc/mark/idle:cpu-seconds

    估计花费在执行GC任务上的总CPU时间闲置的CPU资源,否则Go调度程序无法找到用的时间。
    
    这应该从总的GC CPU时间中减去来衡量强制性的GC CPU时间。
    
    这个指标是高估的,并不能直接与系统CPU时间的测量。
    
    只能与其他/cpu/classes度量。

/cpu/classes/gc/pause:cpu-seconds

    被GC暂停的应用程序所花费的CPU总时间估计。
    暂停的总CPU时间。即使在暂停期间只有一个线程在运行,这也是计算为GOMAXPROCS乘以暂停延时,
    
    因为没有其他的可以执行。
    这是/gc/pause:seconds中样本的精确总和如果每个样本都乘以拍摄时的GOMAXPROCS。
    
    这个指标是一个高估,并不能直接与系统CPU时间的测量。
    只能与其他/cpu/classes度量。

/cpu/classes/gc/total:cpu-seconds

    估计执行GC任务的总CPU时间。
    
    这个指标是一个高估,并不能直接与系统CPU时间的测量。只能与其他/cpu/classes度量。
    /cpu/classes/gc中所有指标的总和。

/cpu/classes/idle:cpu-seconds

    估计总的可用CPU时间,不用于执行任何Go或Go运行时的代码。
    换句话说,/cpu/classes/total:cpu-seconds的部分中未使用的部分。
    
    这个指标是高估的,并不能直接与系统CPU时间的测量。
    只与其他/cpu/classes度量。

/cpu/classes/scavenge/assist:cpu-seconds

    估计CPU花费的总时间,将未使用的内存返回到为了应对内存压力而急切地响应底层平台。
    
    这个指标是一个高估,并不能直接与系统CPU时间的测量。
    只能与其他/cpu/classes度量。

/cpu/classes/scavenge/background:cpu-seconds

    估计CPU执行后台任务的总时间以将未使用的内存返回到底层平台。
    
    这个指标是一个高估,并不能直接与系统CPU时间的测量。只能与其他/cpu/classes度量。

/cpu/classes/scavenge/total:cpu-seconds

    估计CPU在执行任务时花费的总时间,这些任务将未使用的内存返回给底层平台。
    未使用的内存到底层平台。
    
    这个指标是一个高估,并不能直接与系统CPU时间的测量。只能与其他/cpu/classes度量。
    /cpu/classes/scavenge中所有指标的总和。

/cpu/classes/total:cpu-seconds

    用户围棋代码或围棋运行时间的估计总可用CPU时间,如GOMAXPROCS 所定义的。
    换句话说,GOMAXPROCS集成了这个进程执行的该进程执行的壁钟时间。
    
    这个指标是一个高估,并不能直接与系统CPU时间的测量。
    只与其他/cpu/classes度量。/cpu/classes中所有度量的总和。

/cpu/classes/user:cpu-seconds

    估计运行用户Go代码所花费的总CPU时间。
    这可能也包括在go运行时间中花费的少量时间。
    
    这个指标是高估的,并不能直接与系统CPU时间的测量。只能与其他 /cpu/classes度量。

/gc/cycles/automatic:gc-cycles

    Go运行时产生的已完成的GC周期的计数。

/gc/cycles/forced:gc-cycles

    由应用程序强制完成的GC周期的计数。

/gc/cycles/total:gc-cycles

    所有完成的GC周期的计数。

/gc/heap/tiny/allocs:objects,

    按近似大小分配的堆分配。
    
    注意,这不包括/gc/heap/tiny/allocs:objects所定义的微小对象。
    只有微小的块。

/gc/heap/allocs:bytes

    应用程序分配到堆中的内存的累计总和。

/gc/heap/tiny/allocs:objects,

    由应用程序触发的堆分配的累积计数。
    注意,这不包括/gc/heap/tiny/allocs:objects所定义的微小对象。
    只有微小的块。

/gc/heap/tiny/allocs:objects,

    已释放的堆分配的分布情况,按近似的大小。
    
    注意,这不包括/gc/heap/tiny/allocs:objects所定义的微小对象。
    只有微小的块。

/gc/heap/frees:bytes

    被垃圾收集器释放的堆内存的累计总和。

/gc/heap/tiny/allocs:objects,

    被垃圾收集器释放了存储空间的堆分配的累积计数。
    
    注意,这不包括/gc/heap/tiny/allocs:objects所定义的微小对象。
    只有微小的块。

/gc/heap/goal:bytes

    GC周期结束时的堆大小目标。

/gc/heap/objects:objects

    占用堆内存的对象的数量,活的或不活的。

/gc/heap/tiny/allocs:objects

    挤在一起成块的小型分配的计数。
    
    这些分配是与其他分配分开计算的因为每个单独的分配没有被运行时跟踪。
    只有它们的块。每个区块已经被计算在allocs-by-size和frees-by-size。

/gc/limiter/last-enabled:gc-cycle

    上次启用GC CPU限制器时的GC周期。
    
    这个指标对于诊断内存不足的根本原因很有用,错误的根本原因,因为当GC的CPU时间过高时,限制器会用内存换取CPU时间。
    
    时间过高时,限制器用内存换取CPU时间。
    
    这种情况在使用SetMemoryLimit时最容易发生。
    第一个GC周期是周期1,所以数值为0表示它从未被启用。

/gc/pauses:seconds

    与GC相关的个别停顿延迟的分布。

/gc/stack/starting-size:bytes

    给新例程分配的堆大小

/memory/classes/heap/free:bytes

    完全自由的内存,有资格返回给底层系统,但没有被返回的内存,但还没有被返回到底层系统。
    
    这个指标是运行时对由物理内存支持的自由地址空间的估计。
    物理内存支持的自由地址空间。

/memory/classes/heap/objects:bytes

    活的对象和死的对象所占用的内存,这些对象还没有被垃圾收集器标记为空。
    占用的内存,以及尚未被垃圾收集器标记为空的死对象。

/memory/classes/heap/released:bytes

    完全自由的内存,并已返回到底层系统。
    这个指标是运行时对仍然映射到进程中的的自由地址空间,它仍然被映射到进程中,但是没有物理内存的支持。

/memory/classes/heap/stacks:bytes

    从堆中分配的内存被保留为堆栈空间。空间,无论它目前是否在使用中。

/memory/classes/heap/unused:bytes

    为堆对象保留的内存,但目前没有被用来存放堆对象。用来存放堆对象。

/memory/classes/metadata/mcache/free:bytes

    保留给运行时缓存结构的内存,但不在使用中。不是在使用中的。

/memory/classes/metadata/mcache/inuse:bytes

    目前正在使用的运行时缓存结构所占用的内存。目前正在使用。

/memory/classes/metadata/mspan/free:bytes

    保留给运行时mspan结构的内存,但不在使用中。不是在使用中的。

/memory/classes/metadata/mspan/inuse:bytes

    被运行时mspan结构占用的内存,这些结构目前正在使用。目前正在使用。

/memory/classes/metadata/other:bytes

    为运行时保留的或用于保留运行时元数据的内存。元数据。

/memory/classes/os-stacks:bytes

    由底层操作系统分配的堆栈内存。

/memory/classes/other:bytes

    执行跟踪缓冲区使用的内存,用于调试运行时的结构调试运行时的结构,最后一个程序和剖析器的特殊性,等等。

/memory/classes/profiling/buckets:bytes

    堆栈跟踪哈希图所使用的内存,用于剖析。

/memory/classes/total:bytes

    所有由Go运行时映射到当前进程的内存作为读写器。
    
    请注意,这并不包括通过cgo或syscall包调用的代码所映射的内存通过 cgo 或 syscall 包调用的代码映射的内存。
    
    /memory/classes中所有指标的总和。

/sched/gomaxprocs:threads

    当前运行时.GOMAXPROCS的设置,或者说可以执行用户级Go代码的操作系统线程数。
    
    可以同时执行用户级 Go 代码的操作系统线程数同时进行。

/sched/goroutines:goroutines

    活跃的协程数量。

/sched/latencies:seconds

    程式在实际运行前在调度器中停留的时间分布
    在实际运行前处于可运行状态的时间分布。

/sync/mutex/wait/total:seconds

    goroutines在sync.Mutex或sync.RWMutex上被阻塞的大约累计时间。

    sync.Mutex或sync.RWMutex上阻塞的大约时间。这个指标对于识别锁争夺的全局变化。
    收集一个Mutex或块使用 runtime/pprof 软件包来收集更详细的争夺数据。
  • Float64Histogram表示float64值的分布。

      Buckets包含直方图桶的边界,按递增顺序排列。
    
      Buckets[0]是最小桶的包容性下界,而
      Buckets[len(Buckets)-1]是最大桶的专属上界。
    

因此,有len(Buckets)-1个计数。此外,len(Buckets) != 1,总是如此。
因为至少需要两个边界来描述一个桶(而0边界用于描述0个桶)。

Buckets[0]被允许有-Inf的值,Buckets[len(Buckets)-1]被允许有Inf值。

对于一个给定的度量名称,Buckets的值保证在调用之间不会改变,直到程序退出。
在两次调用之间,直到程序退出。

这个片断的值允许与其他 Float64Histograms 的 Buckets字段,所以里面的值只能被读取。

如果它们需要被修改,用户必须制作一个副本,Counts包含每个直方图桶的权重。

给定N个桶,Count[n]是范围内的权重

  [bucket[n], bucket[n+1]),为0 <= n < N。

Sample Read 采样读取在给定的公制样本片中的每个Value字段。

所期望的度量应该存在于具有适当名称的片断中。

我们鼓励这个API的用户在不同的调用中重复使用同一个片断,以提高效率,但不必须这样做。

注意,重复使用有一些注意事项。值得注意的是,不应该在读取或当一个带有该值的读操作尚未完成时,不应该对该值进行操作;那是一种数据竞争。

这个属性包括指针类型的值(例如,Float64Histogram)。
它们的底层存储将尽可能地被Read重用。为了安全地使用这种值,所有的数据必须被深度复制。

并发执行多个Read调用是安全的,但是它们的参数必须不共享底层内存。

如果有疑问,可以从头开始创建一个新的[]样本。从头开始,这总是安全的,虽然可能效率不高。

如果Sample的名字没有出现在All中,那么它的Value将被填充为KindBad,以表明它是不存在的。
作为KindBad,以表明该名称是未知的。

9 变量注册 runtime msan asan

cgo 相关 和 注册全局变量。

10 覆盖率分析 runtime coverage

  • 函数 WriteMetaDir

为当前正在运行的程序写一个覆盖元数据文件到’dir’指定的目录中。

运行的程序写入’dir’中指定的目录。如果该操作不能成功完成,将返回一个错误,如果该操作不能成功完成。
将返回一个错误(例如,如果当前运行的程序没有用"-cover",或者该目录不存在)。

  • 函数 emitState
    emitState在发射过程中保存有用的状态信息。

当一个被检测的程序完成执行并开始覆盖数据的过程中,有可能在输出目录中已经存在一个现有的元数据文件。

在这种情况下,下面的openOutputFiles()会将下面的’mf’字段保留为零。
如果需要一个新的元数据文件,'mfname’字段将是元文件的最终路径,
‘mftmp’将是一个 临时文件,而’mf’将是一个打开的os.File指针,用来指向’mftmp’。

元数据文件的有效载荷将被写入’mf’,这个然后关闭临时文件并重新命名(从’mftmp’到mfname’),以确保元数据文件是以原子方式创建的。

我们希望这样做,
以便在以下情况下工作顺利:
以便在一个给定的被测程序有多个实例的情况下顺利进行。
一个给定的工具程序有几个实例,都在同一时间终止,并试图同时创建元数据文件。

对于计数器数据文件来说,发生碰撞的可能性较小,因此openOutputFiles()将计数器数据文件存储在’cfname’中,并然后把*io.File放到’cf’中。

initHooks 仅由编译器调用。
initHook从主包的 "init “例程中被调用。用”-cover "构建的程序中调用。这个函数的目的是

如果’istest’为假,表明我们正在构建一个普通程序。
(“go build -cover …”),在这种情况下,我们会立即尝试写入,写出元数据文件,并将 emitCounterData 注册为一个退出的钩子。

如果’istest’为真(表明该程序是一个Go测试二进制文件),那么我们就暂且将emitMetaData和emitCounterData作为退出钩子。
在正常情况下(例如,常规的 "gotest -cover "运行),testmain.go模板将在测试结束时运行。
最后,写出覆盖率,然后调用markProfileEmitted(),以表明没有更多的工作需要完成。
然而,如果这个调用没有被执行,这就说明了测试二进制文件被用作工具的替代二进制文件。
因此,我们希望在程序终止时运行退出钩子。然后终止。

11 runtime cgo

cgo包含对cgo工具生成的代码的运行时支持的运行支持。 有关使用cgo的细节,请参见cgo命令的文档以了解使用cgo的细节。

12 剖析工具 runtime pprof

以pprof可视化工具期望的格式写入运行时剖析数据。以格式化写入运行时剖析数据,并由 pprof 可视化工具进行处理。

  • 解析一个go程序

剖析Go程序的第一步是启用剖析功能。 支持对用标准测试构建的基准进行剖析。
此包内置在go test中。例如,下面的命令 在当前目录下运行基准,并将CPU和 内存配置文件到 cpu.prof 和 mem.prof。

go test -cpuprofile cpu.prof -memprofile mem.prof -bench .

也有一个标准的HTTP接口来获取剖析数据。添加 以下一行将在/debug/pprof/下安装处理程序。

URL下安装处理程序,以下载实时配置文件。

    import _ "net/http/pprof"

更多细节请参见 net/http/pprof 包。 然后,配置文件可以用pprof工具进行可视化。

    go tool pprof cpu.prof

在pprof命令行中有许多命令可以使用。 常用的命令包括 "top "和 “web”,

    "top "可以打印出程序热点的摘要。
     "web",它可以打开一个交互式的热点及其调用图。   

热点和它们的调用图。使用 "help "可以获得关于 所有的pprof命令。

简介是一个堆栈痕迹的集合,显示了导致特定事件的调用序列。 导致一个特定事件的实例,如分配。

软件包可以创建和维护它们自己的配置文件;最常见的用途是跟踪必须明确关闭的资源,如文件。

最常见的用途是跟踪必须明确关闭的资源,如文件 或网络连接。

        type Profile struct {
                        name  string
                        mu    sync.Mutex
                        m     map[any][]uintptr
                        count func() int
                        write func(io.Writer, int) error
                }

一个Profile的方法可以同时从多个goroutine中调用。

每个配置文件都有一个独特的名称。有几个配置文件是预定义的。

        goroutine    - stack traces of all current goroutines
        heap         - a sampling of memory allocations of live objects
        allocs       - a sampling of all past memory allocations
        threadcreate - stack traces that led to the creation of new OS threads
        block        - stack traces that led to blocking on synchronization primitives
        mutex        - stack traces of holders of contended mutexes

这些预定义的配置文件会自我维护,并在一个明确的 添加或删除方法的调用时发生恐慌。
堆概况报告了最近完成的垃圾收集的统计数据。

垃圾收集;它忽略了最近的分配,以避免使 它忽略了最近的分配,以避免使概况偏离实时数据而偏向垃圾。
如果根本就没有进行过垃圾收集,那么堆概况就会报告 所有已知的分配。

这种例外情况主要在程序运行时有帮助启用垃圾收集的情况下运行,通常是为了调试。

堆配置文件跟踪了所有活的对象在应用程序内存中的分配位置以及自程序启动以来分配的所有对象。
程序启动后分配的所有对象的分配位置。

    Pprof的-inuse_space, -inuse_objects, -alloc_space, 和 -alloc_objects

标志选择要显示的对象,默认为 -inuse_space (实时对象。默认为 -inuse_space (活的对象,按尺寸缩放)。

allocs配置文件与heap配置文件相同,但将默认的 pprof显示为-alloc_space,即自程序开始以来分配的字节总数。
程序开始以来分配的字节总数(包括收集的垃圾字节)。

CPU配置文件不是作为一个配置文件提供的。它有一个特殊的API。
StartCPUProfile和StopCPUProfile函数,因为它在剖析过程中会流向 因为它在剖析过程中会把输出流到一个写入器上。

    func StartCPUProfile(w io.Writer) error

StartCPU 配置文件启用了当前进程的CPU剖析。 在进行剖析时,剖析结果将被缓冲并写入w中。
如果已经启用了剖析功能,StartCPUProfile将返回一个错误。

在类似于Unix的系统中,StartCPUProfile在默认情况下对以下情况不起作用 用 -buildmode=c-archive 或 -buildmode=c-shared 构建的 Go 代码。

StartCPUProfile依赖于SIGPROF信号,但该信号会 被传递给主程序的 SIGPROF 信号处理程序(如果有的话)。

而不是Go所使用的那个。

为了使其工作,请调用os/signal.Notify 用于 syscall.SIGPROF,但要注意这样做可能会破坏主程序正在进行的剖析工作。

主程序正在进行的剖析。

运行时例程允许一个可变的剖析率。

但在实践中,操作系统不能触发信号但实际上操作系统不能以超过500ms(hz)的速度触发信号,而我们对信号的处理并不便宜(主要是获取堆栈跟踪)。

100 是一个合理的选择:它足够频繁,足以产生有用的数据,又足够稀少,不会使系统陷入困境。
系统,而且是一个很好的整数,使之容易将样本数转换为秒数。与其要求而不是要求每个客户指定频率,我们对其进行硬编码。

二,小结:

go的1.20以上的 cover该工具现在可以收集整个程序的覆盖率概况,而不仅仅是单元测试。 评估代码单测的覆盖,而runtime包也有较多更新,编译器和垃圾收集器的改进减少了内存开销,并将整体 CPU 性能提高了 2%。。

下一节我们使用runtime 和 unsafe 调整和优化程序,查看几个例子。

参考:

1 关于pprof的更多信息,见

    https://github.com/google/pprof/blob/master/doc/README.md.

2 BUG(rsc): 配置文件只有在生成它们的内核支持下才是好的。

    https://golang.org/issue/13841

3 GC算法的知识遗产包括Dijkstra的on-the-fly算法,见

Edsger W. Dijkstra, Leslie Lamport, A. J. Martin, C. S. Scholten, and E. F. M. Steffens. 1978.
On-the-fly garbage collection: an exercise in cooperation. ACM 21, 11 (November 1978),
966-975.

4 关于GC这些步骤是完整、正确和终止的期刊质量证明,见

Hudson, R., and Moss, J.E.B. Copying Garbage Collection without stopping the world.
Concurrency and Computation: Practice and Experience 15(3-5), 2003.

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。