一、测试背景
Go最新版本引入了实验性的 GreenTeaGC 算法(编译时通过 GOEXPERIMENT=greenteagc 启用),将在1.26版本作为默认的GC算法。最近想就手头的项目,试试看GreenTeaGC相对于标准GC算法的效果,于是就有了这篇文章。
二、测试环境
硬件配置
root@VM-16-14-ubuntu:/data/webp# neofetch
.-/+oossssoo+/-. root@VM-16-14-ubuntu
`:+ssssssssssssssssss+:` --------------------
-+ssssssssssssssssssyyssss+- OS: Ubuntu 24.04.3 LTS x86_64
.ossssssssssssssssssdMMMNysssso. Host: CVM 3.0
/ssssssssssshdmmNNmmyNMMMMhssssss/ Kernel: 6.8.0-88-generic
+ssssssssshmydMMMMMMMNddddyssssssss+ Uptime: 1 day, 35 mins
/sssssssshNMMMyhhyyyyhmNMMMNhssssssss/ Packages: 913 (dpkg), 4 (snap)
.ssssssssdMMMNhsssssssssshNMMMdssssssss. Shell: bash 5.2.21
+sssshhhyNMMNyssssssssssssyNMMMysssssss+ Resolution: 1024x768
ossyNMMMNyMMhsssssssssssssshmmmhssssssso Terminal: /dev/pts/0
ossyNMMMNyMMhsssssssssssssshmmmhssssssso CPU: AMD EPYC 7K62 (2) @ 2.595GHz
+sssshhhyNMMNyssssssssssssyNMMMysssssss+ GPU: 00:02.0 Cirrus Logic GD 5446
.ssssssssdMMMNhsssssssssshNMMMdssssssss. Memory: 1886MiB / 3659MiB
/sssssssshNMMMyhhyyyyhdNMMMNhssssssss/
+sssssssssdmydMMMMMMMMddddyssssssss+
/ssssssssssshdmNNNNmyNMMMMhssssss/
.ossssssssssssssssssdMMMNysssso.
-+sssssssssssssssssyyyssss+-
`:+ssssssssssssssssss+:`
.-/+oossssoo+/-.
软件环境
Go 版本: 1.25.4
GC 配置: GreenTeaGC:
GOEXPERIMENT=greenteagc
测试应用
TCP 长连接压测套件
本测试使用自研的 TCP 压测工具,模拟大规模 IoT 设备客户端连接场景:
基本架构:
角色: TCP 客户端 → IoT 服务端
连接类型: TCP 长连接(保持连接不断开)
交互流程:
登录阶段: 客户端发送登录指令,等待服务端响应确认
心跳保活: 每 5 分钟发送一次心跳指令,保持连接活性
位置上报: 每 10 分钟上报一次位置数据(模拟生成的位置信息)
同步通信: 所有指令交互采用同步模式(发送 → 等待回复 → 继续)
内存分配特点:
连接建立后保持长连接,内存相对稳定
定时指令交互产生少量临时对象(心跳包、位置数据)
低频内存分配场景(5-10 分钟间隔)
测试场景
场景一: 3万 TCP 长连接
场景二: 5万 TCP 长连接
测试时长: 每个场景 1小时
监控间隔: 5秒/次
监控指标
GC 频率、暂停时间
CPU 使用率(总CPU使用率)
内存使用(堆内存)
Goroutine 数量
三、测试数据
3.1 三万连接场景
3.2 五万连接场景
3.3 扩展性对比(3万 → 5万)
四、数据分析
4.1 GC 性能
GC 频率: 两种 GC 策略的频率基本相同(~0.008次/秒);
GC 暂停:
3万连接: 平均暂停时间相同,GreenTeaGC 最大暂停降低 20%;
5万连接: GreenTeaGC 平均暂停降低 11%,但最大暂停增加 49%。
结论: GC 暂停时间方面,无明显优势。
4.2 CPU 消耗
总 CPU: GreenTeaGC 略高 2-3%(统计误差范围内);
结论: CPU 使用方面,基本持平。
4.3 内存使用
堆内存: 两种策略的内存使用几乎完全相同;
结论: 内存使用方面,无差异。
4.4 扩展性
从 3万到 5万连接时,两种 GC 的资源增长倍数基本相同;
结论: 扩展性方面,表现一致。
五、关键发现
5.1 性能对比结论
在本测试场景下(2C4G,3-5万 TCP 长连接):
✅ 相同表现:
GC 频率完全相同;
内存使用基本相同;
CPU 消耗基本持平;
扩展性表现一致。
⚠️ 细微差异:
GreenTeaGC 最大 GC 暂停:3万连接时降低 20%,5万连接时增加 49%;
GreenTeaGC 平均 CPU 高 2-3%(在统计误差范围内)。
5.2 与预期的差异
预期: GreenTeaGC 应显著降低 GC 暂停时间。
实际: 在本测试场景下,两种 GC 策略性能基本相同。
可能原因:
GC 压力较小: 测试中 GC 频率仅 ~0.008次/秒,GC 压力不大,没有给 GreenTeaGC 展示优化效果的空间;
内存分配模式: TCP 长连接场景内存分配相对稳定,定时指令交互(5-10分钟间隔)产生的临时对象极少,GC 压力不大;
低频交互特点: 心跳(5分钟)和位置上报(10分钟)间隔较长,内存分配频率低,未能触发 GreenTeaGC 的优化机制;
测试规模: 2C4G 环境,3-5万连接可能还未达到 GreenTeaGC 发挥优势的规模。
六、测试局限性
6.1 测试场景局限
单一场景: 仅测试了 TCP 长连接场景;
低频交互: 心跳(5分钟)和位置上报(10分钟)间隔较长,缺少高频内存分配场景(如 HTTP 短连接、高并发请求处理);
同步通信: 指令交互采用同步模式(发送-等待-回复),并发压力相对较小;
资源限制: 2C4G 环境可能限制了测试规模。
6.2 监控局限
采样间隔: 5秒间隔可能错过一些短暂的峰值。
七、结论与建议
7.1 测试结论
在 2C4G、3-5万 TCP 长连接、低 GC 压力 的场景下:
GreenTeaGC 与标准 GC 性能基本相同,无显著优势。
7.2 适用性分析
可能受益于 GreenTeaGC 的场景:
高频内存分配场景(如 HTTP 请求处理);
GC 频繁触发的场景(如内存受限环境);
更大规模场景(如 10万+ 连接);
对 GC 延迟敏感的实时系统。
本测试场景特点:
TCP 长连接,连接建立后保持稳定;
低频指令交互(5-10 分钟间隔的心跳和位置上报);
同步通信模式,内存分配频率低;
GC 频率低(~0.008次/秒);
没有给 GreenTeaGC 展示优势的空间。
7.3 后续建议
扩大测试场景:
测试 HTTP 短连接场景(高频内存分配);
测试高并发请求处理场景(异步并发模式);
测试更大规模(10万+ 连接);
测试内存受限环境;
测试高频指令交互场景(秒级或亚秒级交互频率)。
八、GC 监测实现代码
8.1 核心监测逻辑
监测模块基于 Go runtime 提供的 debug.GCStats 和 runtime.MemStats 实现:
// 执行一次采样
func (m *GCMonitor) sample() {
m.sampleCount++
now := time.Now()
sinceStart := now.Sub(m.startTime)
// 读取当前GC统计
var currentGCStats debug.GCStats
debug.ReadGCStats(¤tGCStats)
// 读取内存统计
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
// 计算增量
gcDelta := calculateGCDelta(&m.lastGCStats, ¤tGCStats)
timeDelta := now.Sub(m.lastSample).Seconds()
// 计算 CPU 指标
cpuMetrics := m.calculateCPUMetrics(timeDelta)
// 构建指标快照
snapshot := MetricSnapshot{
Timestamp: now,
SinceStart: formatDuration(sinceStart),
SampleNumber: m.sampleCount,
GCStats: m.extractGCMetrics(¤tGCStats, &gcDelta, timeDelta),
MemStats: m.extractMemoryMetrics(&memStats),
NumGoroutine: runtime.NumGoroutine(),
NumCPU: runtime.NumCPU(),
CPUMetrics: cpuMetrics,
}
// 写入日志和JSON
m.writeToLog(&snapshot)
if m.config.EnableJSON {
m.writeToJSON(&snapshot)
}
// 更新上次数据
m.lastGCStats = currentGCStats
m.lastSample = now
}
8.2 GC 指标提取
// 提取GC指标
func (m *GCMonitor) extractGCMetrics(stats *debug.GCStats, delta *GCDelta, timeDelta float64) GCMetrics {
metrics := GCMetrics{
NumGC: uint32(stats.NumGC),
NumGCDelta: delta.NumGC,
PauseTotal: float64(stats.PauseTotal) / 1e6, // 转换为毫秒
PauseDelta: delta.PauseTotal / 1e6,
}
// 计算平均暂停时间
if stats.NumGC > 0 {
metrics.PauseAvg = metrics.PauseTotal / float64(stats.NumGC)
}
if delta.NumGC > 0 {
metrics.PauseDeltaAvg = metrics.PauseDelta / float64(delta.NumGC)
}
// 最近一次暂停
if len(stats.Pause) > 0 {
metrics.LastPause = float64(stats.Pause[0]) / 1e6
}
// GC频率
if timeDelta > 0 {
metrics.GCRate = float64(delta.NumGC) / timeDelta
}
return metrics
}
8.3 增量计算
// 计算GC增量
func calculateGCDelta(last, current *debug.GCStats) GCDelta {
delta := GCDelta{
NumGC: 0,
PauseTotal: 0,
}
if current.NumGC > last.NumGC {
delta.NumGC = uint32(current.NumGC - last.NumGC)
delta.PauseTotal = float64(current.PauseTotal - last.PauseTotal)
}
return delta
}
8.4 内存指标提取
// 提取内存指标
func (m *GCMonitor) extractMemoryMetrics(stats *runtime.MemStats) MemoryMetrics {
return MemoryMetrics{
HeapAlloc: stats.HeapAlloc / 1024 / 1024, // 当前堆分配
HeapSys: stats.HeapSys / 1024 / 1024, // 堆系统内存
HeapInuse: stats.HeapInuse / 1024 / 1024, // 堆正在使用
HeapIdle: stats.HeapIdle / 1024 / 1024, // 堆空闲
HeapReleased: stats.HeapReleased / 1024 / 1024, // 已归还系统
TotalAlloc: stats.TotalAlloc / 1024 / 1024, // 总分配量
Sys: stats.Sys / 1024 / 1024, // 系统内存
NextGC: stats.NextGC / 1024 / 1024, // 下次GC阈值
NumObjects: stats.Mallocs - stats.Frees, // 当前对象数
StackInuse: stats.StackInuse / 1024 / 1024, // 栈使用
StackSys: stats.StackSys / 1024 / 1024, // 栈系统内存
}
}
8.5 CPU 监测(总 CPU 使用率)
Unix/Linux 平台
func (m *CPUMonitor) getCPUTime() (userTime, sysTime float64) {
var rusage syscall.Rusage
if err := syscall.Getrusage(syscall.RUSAGE_SELF, &rusage); err != nil {
return 0, 0
}
userSec := float64(rusage.Utime.Sec) + float64(rusage.Utime.Usec)/1e6
sysSec := float64(rusage.Stime.Sec) + float64(rusage.Stime.Usec)/1e6
return userSec, sysSec
}
CPU 使用率计算
func (m *CPUMonitor) GetUsage() (totalPercent, userPercent, sysPercent float64) {
currentUser, currentSys := m.getCPUTime()
currentTotal := currentUser + currentSys
now := time.Now()
if !m.lastUpdate.IsZero() {
elapsed := now.Sub(m.lastUpdate).Seconds()
if elapsed > 0 {
// CPU时间增量
userDelta := currentUser - m.lastUserTime
sysDelta := currentSys - m.lastSysTime
totalDelta := userDelta + sysDelta
// CPU使用率 = CPU时间增量 / 实际时间 * 100
totalPercent = (totalDelta / elapsed) * 100
userPercent = (userDelta / elapsed) * 100
sysPercent = (sysDelta / elapsed) * 100
}
}
m.lastUserTime = currentUser
m.lastSysTime = currentSys
m.lastTotalTime = currentTotal
m.lastUpdate = now
return
}
8.6 日志输出格式
func (m *GCMonitor) writeToLog(snapshot *MetricSnapshot) {
line := fmt.Sprintf(
"[%s] [%10s] #%-4d | GC: %5d (+%-2d) Pause: %8.2fms (+%6.2fms / avg %5.2fms) Rate: %.2f/s | Mem: Heap %5dMB | CPU: %.1f%% | Goroutine: %5d\n",
snapshot.Timestamp.Format("15:04:05"),
snapshot.SinceStart,
snapshot.SampleNumber,
snapshot.GCStats.NumGC,
snapshot.GCStats.NumGCDelta,
snapshot.GCStats.PauseTotal,
snapshot.GCStats.PauseDelta,
snapshot.GCStats.PauseDeltaAvg,
snapshot.GCStats.GCRate,
snapshot.MemStats.HeapAlloc,
snapshot.CPUMetrics.TotalUsage,
snapshot.NumGoroutine,
)
m.logFile.WriteString(line)
m.logFile.Sync()
}
8.7 关键数据结构
// 指标快照
type MetricSnapshot struct {
Timestamp time.Time // 采样时间
SinceStart string // 距启动时间
SampleNumber int // 采样序号
GCStats GCMetrics // GC统计
MemStats MemoryMetrics // 内存统计
NumGoroutine int // Goroutine数量
NumCPU int // CPU核心数
CPUMetrics CPUMetrics // CPU指标
}
// GC指标
type GCMetrics struct {
NumGC uint32 // 总GC次数
NumGCDelta uint32 // 本周期GC次数
PauseTotal float64 // 总暂停时间(ms)
PauseDelta float64 // 本周期暂停时间(ms)
PauseAvg float64 // 平均暂停时间(ms)
PauseDeltaAvg float64 // 本周期平均暂停(ms)
LastPause float64 // 最近一次暂停(ms)
GCRate float64 // GC频率(次/秒)
}
// 内存指标
type MemoryMetrics struct {
HeapAlloc uint64 // 堆分配(MB)
HeapSys uint64 // 堆系统内存(MB)
HeapInuse uint64 // 堆正在使用(MB)
HeapIdle uint64 // 堆空闲(MB)
HeapReleased uint64 // 已归还系统(MB)
TotalAlloc uint64 // 总分配(MB)
Sys uint64 // 系统内存(MB)
NextGC uint64 // 下次GC阈值(MB)
NumObjects uint64 // 对象数量
StackInuse uint64 // 栈使用(MB)
StackSys uint64 // 栈系统内存(MB)
}
// CPU指标
type CPUMetrics struct {
TotalUsage float64 // 总CPU使用率(%)
}