仓颉 std.runtime 包函数 API 全解析:GC 调控、堆内存监测与 CPU 性能剖析

原创2026-10-13 20:30:361,411 阅读
文章标签:文档知识库

仓颉 std.runtime 包函数 API 全解析:GC 调控、堆内存监测与 CPU 性能剖析

std.runtime 包是仓颉(Cangjie)语言标准库中与程序运行时环境交互的入口,围绕自动垃圾回收(GC)机制提供了手动触发 GC、设置 GC 触发阈值、读取堆内存与线程/处理器统计信息、生成堆快照以及 CPU 性能剖析等 17 个全局函数。本文基于 runtime_package_funcs.md 逐一解析这些函数的签名、参数、返回值与异常语义,并结合同包结构体文档与运行时环境变量手册补充底层原理,帮助你精确掌控程序的内存与性能状态。

一、std.runtime 包概览:为什么需要与运行时交互

仓颉语言使用自动垃圾回收机制管理内存,开发者通常无需手动释放对象。然而在以下场景中,直接与运行时交互变得必要:

  • 在内存敏感型服务中,希望在业务低峰期主动触发一次 GC,减少高峰期停顿;
  • 在长时间运行的程序中,需要监测堆内存使用量、GC 触发频率与回收效率,判断是否存在内存增长异常;
  • 需要诊断 CPU 热点函数,将 profiler 记录导出后分析性能瓶颈。

std.runtime 包正是为这类需求设计的。根据 runtime_package_overview.md 的介绍,该包的作用是“与程序的运行时环境进行交互,提供了一系列函数和变量,用于控制、管理和监视程序的执行”,并针对 GC 提供“手动触发垃圾回收、设置垃圾回收的阈值、获取内存统计信息等功能”。

在使用这些 API 之前,需要通过 import std.runtime.* 引入包,例如:

import std.runtime.*
main() {
    println(getProcessorCount())
}

包内的全部能力分为两类:全局函数(17 个,见 runtime_package_funcs.md)与结构体(3 个已废弃,见 runtime_package_structs.md)。整体 API 清单如下表:

函数名 功能
dumpHeapData(Path) 生成堆内存快照信息,写入指定路径的文件
GC(Bool) <sup>(deprecated) 执行 GC(即将废弃)
gc(Bool) 执行 GC
getAllocatedHeapSize 获取仓颉堆已被使用的大小(byte)
getBlockingThreadCount 获取阻塞的仓颉线程数
getGCCount 获取触发 GC 的次数
getGCFreedSize 获取 GC 成功回收的内存(byte)
getGCTime 获取 GC 总耗时(us)
getMaxHeapSize 获取仓颉堆可使用的最大值(byte)
getNativeThreadCount 获取物理线程数
getProcessorCount 获取处理器数量
getThreadCount 获取仓颉当前线程数量
getUsedHeapSize 获取堆/进程实际占用的物理内存(byte)
SetGCThreshold(UInt64) <sup>(deprecated) 修改触发 GC 的内存阈值(KB,即将废弃)
setGCThreshold(UInt64) 修改触发 GC 的内存阈值(KB)
startCPUProfiling 启动 CPU profiler 跟踪
stopCPUProfiling(Path) 停止 CPU profiler 跟踪并写入记录文件

二、从结构体到全局函数:API 的演进与替代关系

在早期的 std.runtime 设计中,堆内存、处理器与线程的统计能力分别由 MemoryInfo、ProcessorInfo、ThreadInfo 三个结构体的静态属性提供(定义见 runtime_package_structs.md)。例如通过 MemoryInfo.maxHeapSize 读取堆上限:

import std.runtime.*
main() {
    println(MemoryInfo.maxHeapSize)
}

运行结果(以实际环境为准):

268435456

如今这三个结构体均已标记为 deprecated,官方在文档中明确给出了替代方案:

类似地,函数层面也存在大小写命名的新旧交替:GC 与 SetGCThreshold 同样标记为 deprecated,未来版本将由小写命名的 gc 与 setGCThreshold 取代。新代码应直接使用小写命名的新函数,避免在后续版本升级时产生兼容性问题。

三、手动触发垃圾回收:gc 与 GC

仓颉的 GC 由运行时自动调度,但 std.runtime 允许开发者显式发起一次回收,用于在可控时机释放内存。

gc(heavy!: Bool = false)

public func gc(heavy!: Bool = false): Unit
  • heavy! 参数:布尔类型,控制 GC 的执行程度。当为 true 时,执行会变慢,但内存收集得更多;默认值为 false。
  • 返回值:无(Unit)。

命名参数使用 ! 后缀标注,调用时可省略,即 gc() 等价于 gc(heavy!: false)。典型的主动回收场景是:程序在加载完成一批大对象并清理引用后,手动调用一次 gc(heavy!: true),尽量回收这批对象占用的内存,降低后续运行期的 GC 压力。

GC(heavy!: Bool = false) <sup>(deprecated)

public func GC(heavy!: Bool = false): Unit

这是旧版 API,功能与 gc 完全一致,但文档明确标注“未来版本即将废弃,使用 gc 替代”。两版函数的参数语义相同:heavy! 为 true 时执行慢但收集内存多,默认 false。

四、设置 GC 触发阈值:setGCThreshold 与 SetGCThreshold

仓颉运行时依据“堆水线”决定何时触发 GC:当仓颉堆大小超过阈值时,触发一次垃圾回收。std.runtime 提供了在程序运行期动态修改该阈值的函数。

setGCThreshold(value: UInt64)

public func setGCThreshold(value: UInt64): Unit
  • value 参数:UInt64 类型,用户期望触发 GC 的内存阈值,单位为 KB。
  • 返回值:无。

文档给出的示例为将 GC 阈值设置为 2MB:

import std.runtime.*
main() {
    setGCThreshold(2048)
}

因为单位是 KB,2MB 对应 2048(2 × 1024 KB)。合理调低阈值可以让 GC 更频繁地触发,从而让堆占用保持在较低水平,代价是回收开销增加;调高阈值则减少 GC 频率,适合追求吞吐量的场景。

SetGCThreshold(value: UInt64) <sup>(deprecated)</sup>

public func SetGCThreshold(value: UInt64): Unit

旧版命名,功能与 setGCThreshold 相同,同样以 KB 为单位。文档明确标注“未来版本即将废弃”,新代码应使用小写版本。

与运行时环境变量 cjGCThreshold 的对应关系

除了运行期 API,仓颉运行时还允许在启动前通过环境变量配置 GC 水线。根据 runtime 环境变量使用手册,cjGCThreshold 用于“指定仓颉堆的参考水线值,当仓颉堆大小超过该值时,触发 GC”,支持 kb、mb、gb 等单位,默认值为堆大小。二者配合使用可以形成完整的调控闭环:

export cjGCThreshold=20480KB

启动前的环境变量适合确定程序整体的内存策略,运行期的 setGCThreshold 则适合根据实时业务状态动态微调。此外,同手册中的 cjHeapUtilization(堆利用率,默认 0.8)与 cjHeapGrowth(堆增长率,默认 0.15)也是 GC 后更新堆水线的参考依据,共同决定了触发 GC 的概率高低。

五、堆内存统计与堆快照

这一组函数用于回答“程序现在吃了多少内存、最多能吃多少、堆里发生了什么”等核心问题。

getAllocatedHeapSize()

public func getAllocatedHeapSize(): Int64

返回仓颉堆已被使用的大小,单位为 byte。它反映的是堆内已分配对象(含垃圾对象)占用的逻辑空间。

getUsedHeapSize()

public func getUsedHeapSize(): Int64

返回堆实际占用的物理内存大小,单位为 byte。文档特别强调了平台差异:

  • 在 Linux 平台下,获取的是仓颉堆实际占用的物理内存大小;
  • 在 Windows 及 macOS 平台下,获取的是仓颉进程实际占用的物理内存大小。

这与操作系统的内存计量方式有关:Linux 下可以从进程内存映射中单独区分出堆段,而 Windows/macOS 下则以整个进程的常驻物理内存为统计口径。

getMaxHeapSize()

public func getMaxHeapSize(): Int64

返回仓颉堆可以使用的最大值,单位为 byte,即堆的容量上限。该上限可通过启动前环境变量 cjHeapSize 配置(见 runtime_env.md):支持 kb、mb、gb 单位,设置范围为 [4MB, 系统物理内存],默认值在物理内存低于 1GB 时为 64MB,否则为 256MB。

dumpHeapData(path: Path)

public func dumpHeapData(path: Path): Unit

生成堆内存快照信息,并写入指定路径的文件,是排查堆内对象分布、定位内存泄漏的关键手段。

  • path 参数:Path 类型,指定堆快照文件的保存路径。Path 是 std.fs 包提供的路径结构体,可通过 Path("...") 构造,且实现了 Equatable、Hashable、ToString 接口。
  • 异常:生成堆内存快照失败时抛出 MemoryInfoException。

与堆快照配套的还有两个启动前环境变量(见 runtime_env.md):cjHeapDumpOnOOM(设为 on 时在堆溢出后自动输出堆快照文件)与 cjHeapDumpLog(指定堆快照输出路径,未指定时输出到当前执行目录)。运行期调用 dumpHeapData 与 OOM 自动转储互为补充,前者适合按需采样,后者适合事故兜底。

一个综合使用堆统计与快照的示例:

import std.runtime.*
import std.fs.*

main() {
    println("已使用堆大小(byte): ${getAllocatedHeapSize()}")
    println("实际占用物理内存(byte): ${getUsedHeapSize()}")
    println("堆上限(byte): ${getMaxHeapSize()}")
    dumpHeapData(Path("heap_dump.bin"))
}

六、GC 行为统计:次数、回收量与耗时

监控 GC 是否过于频繁、回收效率如何,需要以下三个统计函数,返回值均为 Int64:

函数 返回值含义 单位
getGCCount() 触发 GC 的次数 次
getGCFreedSize() 触发 GC 后成功回收的内存 byte
getGCTime() 触发的 GC 总耗时 us

通过计算 getGCFreedSize() / getGCCount() 可以得到平均单次回收量,结合 getGCTime() 可估算 GC 的平均停顿时间。例如,若观察到 GC 次数持续快速上涨但单次回收量很小,通常意味着程序存在大量短期存活对象或内存压力过高,此时可配合 setGCThreshold 与堆快照进一步定位。

GC 频率同样可以在启动前通过环境变量约束:cjGCInterval 指定两次 GC 的最小间隔(默认 150ms,若本次 GC 距上次小于该值则被忽略),cjBackupGCInterval 指定 backup GC 的间隔(默认 240 秒,超时未触发 GC 则强制触发一次),详见 runtime_env.md。

七、线程与处理器统计

std.runtime 提供一组线程/处理器信息查询函数,用于评估程序的并发规模与所在机器的算力:

函数 返回值含义
getThreadCount() 仓颉当前的线程数量
getNativeThreadCount() 物理线程数
getBlockingThreadCount() 阻塞的仓颉线程数
getProcessorCount() 处理器数量

其中“物理线程数”与“处理器数量”可用于估算机器的并行能力;getBlockingThreadCount 则能反映程序中有多少仓颉线程正阻塞在等待资源(如锁、IO)上,是分析线程池健康度的直观指标。仓颉线程的最大并发数也可以在启动前通过环境变量 cjProcessorNum 配置,支持范围为 (0, CPU 核数 × 2],默认值为 CPU 核数(获取失败时为 8),见 runtime_env.md。

八、CPU 性能剖析:startCPUProfiling 与 stopCPUProfiling

当需要定位 CPU 热点时,可以使用 profiler 跟踪记录函数调用耗时。std.runtime 提供一对函数:

startCPUProfiling()

public func startCPUProfiling(): Unit

启动 CPU profiler 跟踪,无参数、无返回值。

stopCPUProfiling(path: Path)

public func stopCPUProfiling(path: Path): Unit

停止 CPU profiler 跟踪,并将记录写入指定路径的文件。

  • path 参数:Path 类型,记录文件的保存路径。

严格成对调用的约束与 ProfilingInfoException

这两个函数必须一一对应调用,否则抛出 ProfilingInfoException。具体规则如下:

  • 调用了 startCPUProfiling() 后,在未调用 stopCPUProfiling(Path) 的情况下又调用 startCPUProfiling(),抛出异常;
  • 没有调用过 startCPUProfiling() 而直接调用 stopCPUProfiling(Path),抛出异常。

典型的正确用法是:

import std.runtime.*
import std.fs.*

main() {
    startCPUProfiling()
    // 被测代码段
    var sum: Int64 = 0
    for (i in 0..1000000) {
        sum += i
    }
    stopCPUProfiling(Path("cpu_profile.bin"))
}

建议将分析范围收窄到需要诊断的函数或循环体,避免将启动、初始化等一次性开销计入 profile 数据。记录文件生成后,可结合仓颉配套的性能分析工具进行离线解析(相关工具介绍见 tools 文档)。

九、综合实战:运行时指标采样工具

将上述 API 组合起来,可以快速构建一个运行时指标采样工具,周期性打印内存、GC 与线程状态:

import std.runtime.*
import std.time.*

func sampleRuntimeInfo() {
    println("===== Runtime Snapshot =====")
    println("堆已使用(byte): ${getAllocatedHeapSize()}")
    println("堆实际占用(byte): ${getUsedHeapSize()}")
    println("堆上限(byte): ${getMaxHeapSize()}")
    println("GC 次数: ${getGCCount()}")
    println("GC 回收总量(byte): ${getGCFreedSize()}")
    println("GC 总耗时(us): ${getGCTime()}")
    println("仓颉线程数: ${getThreadCount()}")
    println("物理线程数: ${getNativeThreadCount()}")
    println("阻塞线程数: ${getBlockingThreadCount()}")
    println("处理器数量: ${getProcessorCount()}")
}

main() {
    sampleRuntimeInfo()
    gc(heavy!: true)
    sampleRuntimeInfo()
}

借助这套 API,开发者可以在不借助外部工具的情况下,将运行时状态直接打印到日志或上报到监控系统,为内存泄漏排查、GC 参数调优和性能回归测试提供一手数据。

十、小结

std.runtime 包以 17 个全局函数覆盖了仓颉程序运行时监测与调控的核心需求:

  • GC 调控:手动触发(gc)与运行期动态设置触发阈值(setGCThreshold),配合启动前的 cjGCThreshold、cjHeapUtilization 等环境变量(见 runtime_env.md)形成完整策略;
  • 内存观测:堆使用量、物理占用、堆上限与堆快照(dumpHeapData),以及 GC 次数/回收量/耗时三项统计;
  • 并发观测:仓颉线程数、物理线程数、阻塞线程数与处理器数量;
  • 性能剖析:startCPUProfiling 与 stopCPUProfiling(Path) 成对使用,导出 CPU 热点记录。

使用时要特别注意新旧 API 的命名差异:GC、SetGCThreshold 以及 MemoryInfo、ProcessorInfo、ThreadInfo 三个结构体均已标记为 deprecated,新代码应统一采用小写命名的全局函数形式,并为 dumpHeapData/stopCPUProfiling 传入合法、可写的 Path,避免触发 MemoryInfoException 或 ProfilingInfoException。

登录后查看全文
CangjieCorpus