2团
Published on 2025-11-27 / 3 Visits
0
0

Golang采用GreenTeaGC与标准GC性能对比测试报告

一、测试背景

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 长连接(保持连接不断开)

交互流程:

  1. 登录阶段: 客户端发送登录指令,等待服务端响应确认

  2. 心跳保活: 每 5 分钟发送一次心跳指令,保持连接活性

  3. 位置上报: 每 10 分钟上报一次位置数据(模拟生成的位置信息)

  4. 同步通信: 所有指令交互采用同步模式(发送 → 等待回复 → 继续)

内存分配特点:

  • 连接建立后保持长连接,内存相对稳定

  • 定时指令交互产生少量临时对象(心跳包、位置数据)

  • 低频内存分配场景(5-10 分钟间隔)

测试场景

  • 场景一: 3万 TCP 长连接

  • 场景二: 5万 TCP 长连接

  • 测试时长: 每个场景 1小时

  • 监控间隔: 5秒/次

监控指标

  • GC 频率、暂停时间

  • CPU 使用率(总CPU使用率)

  • 内存使用(堆内存)

  • Goroutine 数量

三、测试数据

3.1 三万连接场景

指标

标准GC

GreenTeaGC

差异

GC 指标

GC 总次数

40

41

+2.5%

平均 GC 频率

0.008 次/秒

0.008 次/秒

0%

平均周期暂停

0.007 ms

0.007 ms

0%

最大周期暂停

1.970 ms

1.570 ms

-20.3%

内存指标

平均堆内存

384.6 MB

384.6 MB

0%

最大堆内存

452 MB

449 MB

-0.7%

CPU 指标

平均总 CPU

7.03%

7.18%

+2.1%

最大总 CPU

16.50%

15.90%

-3.6%

Goroutine

最终数量

89,995

89,896

-0.1%

平均数量

90,610

90,520

-0.1%

最大数量

101,728

101,411

-0.3%

3.2 五万连接场景

指标

标准GC

GreenTeaGC

差异

GC 指标

GC 总次数

41

40

-2.4%

平均 GC 频率

0.008 次/秒

0.008 次/秒

0%

平均周期暂停

0.009 ms

0.008 ms

-11.1%

最大周期暂停

2.060 ms

3.060 ms

+48.5%

内存指标

平均堆内存

628.3 MB

629.3 MB

+0.2%

最大堆内存

730 MB

728 MB

-0.3%

CPU 指标

平均总 CPU

16.35%

16.80%

+2.8%

最大总 CPU

157.80%

169.80%

+7.6%

Goroutine

最终数量

149,821

149,803

0%

平均数量

149,302

149,284

0%

最大数量

161,536

161,546

0%

3.3 扩展性对比(3万 → 5万)

资源类型

标准GC 增长

GreenTeaGC 增长

连接数

1.66x

1.67x

GC 频率

1.04x

1.00x

总 CPU

2.33x

2.34x

堆内存

1.63x

1.64x

四、数据分析

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 策略性能基本相同

可能原因:

  1. GC 压力较小: 测试中 GC 频率仅 ~0.008次/秒,GC 压力不大,没有给 GreenTeaGC 展示优化效果的空间;

  2. 内存分配模式: TCP 长连接场景内存分配相对稳定,定时指令交互(5-10分钟间隔)产生的临时对象极少,GC 压力不大;

  3. 低频交互特点: 心跳(5分钟)和位置上报(10分钟)间隔较长,内存分配频率低,未能触发 GreenTeaGC 的优化机制;

  4. 测试规模: 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.GCStatsruntime.MemStats 实现:

// 执行一次采样
func (m *GCMonitor) sample() {
    m.sampleCount++
    now := time.Now()
    sinceStart := now.Sub(m.startTime)

    // 读取当前GC统计
    var currentGCStats debug.GCStats
    debug.ReadGCStats(&currentGCStats)

    // 读取内存统计
    var memStats runtime.MemStats
    runtime.ReadMemStats(&memStats)

    // 计算增量
    gcDelta := calculateGCDelta(&m.lastGCStats, &currentGCStats)
    timeDelta := now.Sub(m.lastSample).Seconds()

    // 计算 CPU 指标
    cpuMetrics := m.calculateCPUMetrics(timeDelta)

    // 构建指标快照
    snapshot := MetricSnapshot{
        Timestamp:    now,
        SinceStart:   formatDuration(sinceStart),
        SampleNumber: m.sampleCount,
        GCStats:      m.extractGCMetrics(&currentGCStats, &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使用率(%)
}


Comment