Golang编译器未充分利用多核CPU的问题

Golang编译器未充分利用多核CPU的问题 我注意到,每当Go需要编译大型项目(例如GTK)时,都会花费很长时间(以计算机的标准来看,实际上大约只需要5分钟)。在此期间,处理器通常只处于10-20%的繁忙状态,因此我猜测它只使用了一个核心。那么,为什么它不使用更多的核心呢?是因为所有内容都需要在进程中编译,还是我没有设置环境以使用更多核心?

6 回复

Go gc 编译器已被修改以利用并发性。这允许一定程度的并行性。

GTK 是用 C 语言编写的,而 Go 的 cgo 绑定过程在运行 C 编译器时没有并行性。

问题很可能出在 GTK 上。

更多关于Golang编译器未充分利用多核CPU的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢,那么你的意思是,在编译GoTK3时,它不仅仅是编译GoTK3(Go语言部分),而是同时编译了整个GTK(C/C++部分)?这就是为什么它需要额外的时间并且只使用一个核心的原因吗?我的理解正确吗?

需要明确的是,只有在第一次时,例如在一个新创建的 GoTK3 项目(执行 go get http://github.com/GoTK3/GoTK3 之后),或者在我机器上的 GTK 更新之后,编译才会花费很长时间。我运行的是 Manjaro,所以一旦有新版本的 GTK 发布,我几乎马上就能收到更新……然后我就必须重新编译每一个 GoTK3 应用程序一次(很慢)……

在那之后,编译速度就非常快了。

不过,我可以接受这种时间损失,这并不那么重要,我只是好奇……

Rob Pike 对大型 C++ 程序的一个微小改动需要 38 分钟来编译感到非常不满。于是他开始设计 Go 语言:Go 编程语言宣传片

Go 的 cgo 会编译 GTK 的组件(它很庞大),然后读取头文件和目标文件以提取类型、链接器及其他信息。C 和 C++ 的编译速度是出了名的慢。将 GTK(它很庞大)链接到 Go 也需要不少工作。

如果有一个大型的 Go 项目(不使用 cgo,即 CGO_ENABLED=0),编译和链接需要很长时间,请告诉我们。

我从源代码编译 Go。我刚刚观看了整个 Go 标准库使用 Go gc 编译器进行编译的过程。所有 4 个核心都很繁忙,经常达到 100% 的使用率。

Go 语言竭尽全力加快编译和链接速度。它会缓存中间结果。如果没有变化,就使用缓存结果;否则,重新编译。

例如:

  1. 列出 hello.go 程序。
  2. 运行 hello.go 程序一次以预热缓存。
  3. 计时并运行 hello.go 程序:real 0m0.103s
  4. 使用 -a 标志覆盖缓存值,计时并运行 hello.go 程序:real 0m2.332s

$ cat hello.go
package main

import "fmt"

func main() {
	fmt.Println("Hello, Gophers!")
}

$ go run hello.go
Hello, Gophers!

$ time go run -v hello.go
Hello, Gophers!
real	0m0.103s
user	0m0.120s
sys	    0m0.040s

$ time go run -v -a hello.go
runtime/internal/sys
internal/unsafeheader
runtime/internal/atomic
internal/cpu
math/bits
runtime/internal/math
unicode/utf8
internal/race
sync/atomic
unicode
internal/bytealg
math
runtime
internal/reflectlite
sync
internal/testlog
errors
sort
io
internal/oserror
strconv
syscall
reflect
time
internal/syscall/unix
internal/syscall/execenv
internal/poll
os
internal/fmtsort
fmt
command-line-arguments
Hello, Gophers!
real	0m2.332s
user	0m6.517s
sys	    0m0.473s
$```

Go编译器(gc)确实支持并行编译,但默认情况下并行度受限于GOMAXPROCS设置。对于大型项目,你可以通过以下方式提高编译时的CPU利用率:

  1. 使用-p标志指定并行编译的任务数:
go build -p 4
  1. 设置GOMAXPROCS环境变量:
GOMAXPROCS=8 go build
  1. 对于GTK这类CGO项目,编译瓶颈可能在C编译器。Go的CGO调用会串行执行gcc/clang,你可以通过设置CGO编译器的并行标志:
# 对于gcc
export CGO_CFLAGS="-flto=auto -pipe"
# 对于make构建系统
export MAKEFLAGS="-j$(nproc)"
  1. 使用go 1.21+的并行编译优化:
go build -p=auto  # 自动检测CPU核心数

示例代码展示如何通过程序控制编译并行度:

// build_parallel.go
package main

import (
    "os"
    "os/exec"
    "runtime"
)

func main() {
    // 设置最大并行度
    runtime.GOMAXPROCS(runtime.NumCPU())
    
    cmd := exec.Command("go", "build", "-p", "8", "./...")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()
}

对于GTK这类包含大量C依赖的项目,建议使用分离构建:

# 先并行编译C依赖
cd /path/to/gtk
make -j$(nproc)

# 再编译Go部分
go build -p $(nproc)

检查当前编译资源使用情况:

// check_build.go
package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    go func() {
        for {
            fmt.Printf("Goroutines: %d, CPUs: %d\n", 
                runtime.NumGoroutine(), 
                runtime.GOMAXPROCS(0))
            time.Sleep(1 * time.Second)
        }
    }()
    
    // 触发编译
    // go build...
}

如果问题仍然存在,可能是由于:

  1. 依赖解析阶段是单线程的
  2. 链接阶段存在串行瓶颈
  3. CGO调用导致编译器在等待外部工具

可以通过构建分析确认瓶颈:

go build -x -v 2>&1 | grep -E "(WORK|AR|CC|LD)"
回到顶部