Golang在CentOS 8与CentOS 7中syscall6耗时对比分析

Golang在CentOS 8与CentOS 7中syscall6耗时对比分析 这是一个执行多线程网络I/O的应用程序(用Go编写的用户空间NFS客户端)。它应该能够在10Gb网卡上实现接近线速的数据传输。在CentOS 7上确实如此,10GiB数据大约在10-11秒内完成,然而在CentOS 8上,速度慢了大约2-3秒,稳定在约14秒。

相同的虚拟机规格运行在同一个物理主机上。

项目:GitHub - sile16/fbcp: FlashBlade Fast File transfer tool

命令:

time ./fbcp -threads 16 -sizeMB 8 -profile pp.txt 172.19.0.20:/DEMO-NFS-1/10GRand |cat > /dev/null

Go版本 19.1

以下是各自的性能分析文件。

CentOS 7 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

File: fbcp
Type: cpu
Time: Sep 15, 2022 at 8:51am (CDT)
Duration: 11.22s, Total samples = 31.89s (284.32%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top20
Showing nodes accounting for 28.68s, 89.93% of 31.89s total
Dropped 241 nodes (■■■ <= 0.16s)
Showing top 20 nodes out of 90
      flat  flat%   sum%        ■■■   ■■■%
    12.28s 38.51% 38.51%     12.28s 38.51%  runtime/internal/syscall.Syscall6
     5.19s 16.27% 54.78%      5.19s 16.27%  runtime.memclrNoHeapPointers
     4.66s 14.61% 69.39%      4.66s 14.61%  runtime.memmove
     2.68s  8.40% 77.80%      2.68s  8.40%  runtime.futex
     1.02s  3.20% 81.00%      1.02s  3.20%  runtime.epollwait
     0.64s  2.01% 83.00%      0.64s  2.01%  runtime.madvise
     0.43s  1.35% 84.35%      1.12s  3.51%  runtime.stealWork
     0.36s  1.13% 85.48%      0.36s  1.13%  runtime.(*randomEnum).next (inline)
     0.19s   0.6% 86.08%      0.20s  0.63%  runtime.casgstatus
     0.18s  0.56% 86.64%      6.14s 19.25%  runtime.findRunnable
     0.16s   0.5% 87.14%      0.21s  0.66%  runtime.lock2
     0.12s  0.38% 87.52%      9.39s 29.44%  internal/poll.(*FD).Read
     0.12s  0.38% 87.90%      0.16s   0.5%  runtime.checkTimers
     0.12s  0.38% 88.27%     12.40s 38.88%  syscall.RawSyscall6
     0.11s  0.34% 88.62%      1.22s  3.83%  runtime.notesleep
     0.11s  0.34% 88.96%      0.20s  0.63%  runtime.reentersyscall
     0.10s  0.31% 89.28%      1.16s  3.64%  runtime.netpoll
     0.08s  0.25% 89.53%      5.64s 17.69%  runtime.mallocgc
     0.07s  0.22% 89.75%      9.48s 29.73%  net.(*conn).Read
     0.06s  0.19% 89.93%      9.76s 30.61%  bufio.(*Reader).Read
(pprof) quit

CentOS 8 4.18.0-408.el8.x86_64 #1 SMP Mon Jul 18 17:42:52 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Type: cpu
Time: Sep 15, 2022 at 8:51am (CDT)
Duration: 14.32s, Total samples = 27.91s (194.87%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top20
Showing nodes accounting for 25.39s, 90.97% of 27.91s total
Dropped 213 nodes (■■■ <= 0.14s)
Showing top 20 nodes out of 69
      flat  flat%   sum%        ■■■   ■■■%
    15.77s 56.50% 56.50%     15.77s 56.50%  runtime/internal/syscall.Syscall6
     3.15s 11.29% 67.79%      3.15s 11.29%  runtime.memclrNoHeapPointers
     3.14s 11.25% 79.04%      3.14s 11.25%  runtime.memmove
     1.24s  4.44% 83.48%      1.24s  4.44%  runtime.futex
     1.01s  3.62% 87.10%      1.01s  3.62%  runtime.epollwait
     0.19s  0.68% 87.78%      0.45s  1.61%  runtime.stealWork
     0.11s  0.39% 88.18%      3.43s 12.29%  runtime.findRunnable
     0.11s  0.39% 88.57%      1.15s  4.12%  runtime.netpoll
     0.11s  0.39% 88.96%     15.88s 56.90%  syscall.RawSyscall6
     0.08s  0.29% 89.25%      0.15s  0.54%  runtime.exitsyscall
     0.08s  0.29% 89.54%      3.41s 12.22%  runtime.mallocgc
     0.07s  0.25% 89.79%     10.97s 39.30%  bufio.(*Reader).Read
     0.06s  0.21% 90.00%     10.59s 37.94%  internal/poll.(*FD).Read
     0.06s  0.21% 90.22%     10.70s 38.34%  net.(*conn).Read
     0.05s  0.18% 90.40%     11.03s 39.52%  io.ReadAtLeast
     0.04s  0.14% 90.54%     10.30s 36.90%  syscall.read
     0.03s  0.11% 90.65%      0.16s  0.57%  github.com/rasky/go-xdr/xdr2.(*Encoder).encodeStruct
     0.03s  0.11% 90.76%     10.64s 38.12%  net.(*netFD).Read
     0.03s  0.11% 90.86%      3.61s 12.93%  runtime.schedule
     0.03s  0.11% 90.97%      0.52s  1.86%  runtime.stopm

更多关于Golang在CentOS 8与CentOS 7中syscall6耗时对比分析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你好,@sile16,欢迎来到论坛。

虽然你可能会在 Go Bridge 论坛上找到答案,但我认为你的问题技术性很强,值得在 GitHub 的 Go 项目 issue 跟踪器 上发布一个问题。祝你好运!

编辑: 在搜索了类似问题后,我找到了这个,它被关闭了,因为 issue 跟踪器不是提问的地方(在那个案例中,是关于为什么不同内核版本之间内存使用情况不同),我怀疑他们可能会对你关于运行时的问题做同样处理。当然,你仍然可以尝试提问,但也许 StackOverflow 或 Go 团队在其问题页面上列出的其他网站会更合适。

我最后想说的是,我并不想阻止你在这个论坛提问,但我认为,因为你的问题似乎与 Linux 内核版本有关,你可能需要让更多人看到你的问题,才能找到拥有可以复现你场景的环境的人。

更多关于Golang在CentOS 8与CentOS 7中syscall6耗时对比分析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从性能分析数据来看,CentOS 8中runtime/internal/syscall.Syscall6的耗时占比显著增加(从38.51%上升到56.50%),这是导致性能差异的关键因素。这通常与内核系统调用机制或虚拟化层的变化有关。

问题分析

  1. 系统调用开销差异:CentOS 8使用4.18内核,而CentOS 7使用3.10内核。4.18内核在系统调用路径、Spectre缓解措施等方面有显著变化,可能增加了系统调用开销。

  2. 虚拟化环境因素:虽然使用相同的物理主机,但CentOS 8虚拟机可能受到不同的虚拟化配置影响。

解决方案

1. 减少系统调用频率

使用更大的缓冲区减少read系统调用次数:

// 在创建连接时设置更大的读取缓冲区
conn, err := net.Dial("tcp", address)
if err != nil {
    return err
}

// 设置socket缓冲区大小
if tcpConn, ok := conn.(*net.TCPConn); ok {
    tcpConn.SetReadBuffer(1 * 1024 * 1024) // 1MB缓冲区
    tcpConn.SetWriteBuffer(1 * 1024 * 1024)
}

2. 使用syscall.RawSyscall6替代

对于性能关键路径,考虑直接使用raw系统调用:

import (
    "syscall"
    "unsafe"
)

func rawRead(fd int, p []byte) (n int, err error) {
    var _p0 unsafe.Pointer
    if len(p) > 0 {
        _p0 = unsafe.Pointer(&p[0])
    }
    r0, _, e1 := syscall.RawSyscall6(
        syscall.SYS_READ,
        uintptr(fd),
        uintptr(_p0),
        uintptr(len(p)),
        0, 0, 0,
    )
    n = int(r0)
    if e1 != 0 {
        err = e1
    }
    return
}

3. 调整Go运行时参数

在程序启动时设置环境变量优化系统调用:

export GOMAXPROCS=16
export GODEBUG=asyncpreemptoff=1
export GOGC=50

4. 内核参数调优

在CentOS 8上调整内核参数:

# 增加socket缓冲区大小
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.core.rmem_default=16777216
sysctl -w net.core.wmem_default=16777216

# 调整TCP参数
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
sysctl -w net.ipv4.tcp_window_scaling=1

# 减少上下文切换开销
sysctl -w kernel.sched_min_granularity_ns=10000000
sysctl -w kernel.sched_wakeup_granularity_ns=15000000

5. 使用io_uring(如果内核支持)

对于支持io_uring的CentOS 8内核(5.1+),可以使用新的异步I/O接口:

// 需要第三方库如github.com/iceber/iouring-go
import "github.com/iceber/iouring-go"

func readWithIOUring() {
    ur, err := iouring.New(1024)
    if err != nil {
        panic(err)
    }
    defer ur.Close()
    
    // 使用io_uring进行异步读取
    // ... 具体实现取决于实际需求
}

6. 性能对比测试代码

添加性能监控代码来量化系统调用开销:

import (
    "runtime"
    "time"
)

type SyscallMonitor struct {
    start time.Time
    calls int64
}

func (m *SyscallMonitor) Begin() {
    m.start = time.Now()
}

func (m *SyscallMonitor) End() {
    m.calls++
    if m.calls%1000 == 0 {
        elapsed := time.Since(m.start)
        callsPerSec := float64(m.calls) / elapsed.Seconds()
        runtime.KeepAlive(callsPerSec) // 防止被优化掉
    }
}

// 在关键系统调用前后使用
var monitor SyscallMonitor

func monitoredRead(fd int, buf []byte) (int, error) {
    monitor.Begin()
    defer monitor.End()
    return syscall.Read(fd, buf)
}

验证方法

  1. 使用perf工具分析系统调用延迟:
perf stat -e syscalls:sys_enter_read,syscalls:sys_exit_read ./fbcp
  1. 对比strace输出:
strace -c -e read ./fbcp

这些优化措施应该能显著减少CentOS 8上的系统调用开销,使性能接近CentOS 7的水平。关键是通过缓冲区优化减少系统调用次数,以及适当调整内核参数。

回到顶部