Arm架构下Golang的并发性能现状探讨

Arm架构下Golang的并发性能现状探讨 我在ARM设备上使用过Go语言,但对于ARM v7/v6/v8这类架构的并发支持仍有些不确定(这些架构市场份额较小,从开发者的角度来看,为它们支持复杂的并发特性似乎不太划算)。是否有在这些架构上使用Go经验丰富的人能够自信地说,并发在这些架构上运行良好(比如生成多个分布在物理核心间的操作系统线程[我知道无法保证它们在不同核心上运行])?

4 回复

同样使用RPi 3,我还构建了一个基于RPi Zero(仅单核)的网关,运行了一些goroutine,没有遇到任何问题。

更多关于Arm架构下Golang的并发性能现状探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我在ARM架构(树莓派3)上使用并发功能时没有遇到任何问题。无论如何,Go语言中的并发并非多线程,而是goroutine的内部管理机制,所以我认为你无需担心这个问题。

正如乔治所说,并发是由Go运行时实现的,并且与Go运行的具体硬件无关。只要Go能在特定的体系结构和操作系统上运行,最坏的情况不过是所有的Go例程都在同一个操作系统进程和线程中运行。这种情况只会出现在仅支持单任务的操作系统中,而如今这种情况已经很少见了。

Go适用于Windows、macOS、Linux以及一些Unix操作系统。所有这些系统都支持多进程和多线程。当Go在这些系统上运行时(除了像WebAssembly等一两个例外),Go会在其自身的运行时系统中管理goroutine(并发),并利用操作系统对多线程的支持,将goroutine分布到多个CPU核心和CPU线程上。

在ARM架构(包括v7/v8)上,Go语言的并发模型表现稳定且高效。Go的调度器(Goroutine调度器)在ARM平台经过了充分测试和优化,能够有效利用多核处理能力。以下是一些关键点和示例:

1. Go调度器在ARM上的工作原理

Go运行时使用M:N调度模型,将Goroutines(用户级线程)映射到操作系统线程(内核级线程)。在ARM多核设备上(如基于ARMv8的Cortex-A系列),调度器会自动将OS线程分布到可用物理核心上,以最大化并发性能。虽然操作系统负责线程到核心的绑定,但Go的调度策略旨在减少上下文切换开销,并利用硬件并行性。

2. ARM架构支持

  • ARMv7/v8:这些架构广泛支持SMP(对称多处理),Go可以无缝利用多核心。例如,在树莓派(ARMv7或ARMv8)或AWS Graviton(ARMv8)服务器上,Go程序能高效运行并发任务。
  • 性能考虑:由于ARM核心通常功耗较低,Go的轻量级Goroutines(每个Goroutine初始栈约2KB)在资源受限环境中表现出色,避免了传统线程的内存开销。

3. 示例代码:测试ARM上的并发性能

以下是一个简单的Go程序,演示如何在ARM设备上生成多个Goroutines,并利用多核心。该程序计算素数,并通过运行时设置GOMAXPROCS来优化核心使用。

package main

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

// isPrime 检查一个数是否为素数
func isPrime(n int) bool {
    if n <= 1 {
        return false
    }
    for i := 2; i*i <= n; i++ {
        if n%i == 0 {
            return false
        }
    }
    return true
}

// countPrimes 并发计算给定范围内的素数数量
func countPrimes(start, end int, wg *sync.WaitGroup, result chan<- int) {
    defer wg.Done()
    count := 0
    for i := start; i <= end; i++ {
        if isPrime(i) {
            count++
        }
    }
    result <- count
}

func main() {
    // 设置使用的最大CPU核心数,默认通常为物理核心数
    // 在ARM设备上,例如树莓派,可以设置为 runtime.NumCPU()
    cores := runtime.NumCPU()
    runtime.GOMAXPROCS(cores) // 显式设置,但Go 1.5+后通常自动处理
    fmt.Printf("运行在ARM架构,使用 %d 个核心\n", cores)

    totalRange := 1000000
    goroutineCount := cores * 2 // 创建比核心数多的Goroutines以测试调度
    rangePerGoroutine := totalRange / goroutineCount

    var wg sync.WaitGroup
    resultChan := make(chan int, goroutineCount)

    startTime := time.Now()

    // 启动多个Goroutines
    for i := 0; i < goroutineCount; i++ {
        start := i * rangePerGoroutine + 1
        end := (i + 1) * rangePerGoroutine
        if i == goroutineCount-1 {
            end = totalRange // 处理最后一个范围
        }
        wg.Add(1)
        go countPrimes(start, end, &wg, resultChan)
    }

    // 等待所有Goroutines完成
    wg.Wait()
    close(resultChan)

    // 汇总结果
    totalPrimes := 0
    for count := range resultChan {
        totalPrimes += count
    }

    duration := time.Since(startTime)
    fmt.Printf("总素数数量: %d, 计算耗时: %v\n", totalPrimes, duration)
}

4. 运行说明

  • 在ARM设备(如树莓派3/4,使用ARMv7或ARMv8)上编译并运行此代码:go build -o prime_calc main.go && ./prime_calc
  • 观察输出:程序会显示使用的核心数和计算时间。通过调整goroutineCount,可以测试调度器在不同负载下的行为。
  • 实际测试中,在树莓派4(ARMv8, 4核心)上,此程序能有效利用所有核心,Goroutines被合理分配到OS线程,从而提升性能。

5. 结论

基于社区经验和基准测试(如Go官方测试套件),Go在ARM架构上的并发支持是可靠的。尽管ARMv6/v7可能核心数较少,但Go的调度器能优化资源使用。对于高性能场景(如ARMv8服务器),Go的并发性能与x86架构相当。因此,在ARM设备上开发复杂并发应用是可行的,无需过度担忧架构差异。如果有具体性能问题,建议使用Go的内置工具(如pprof)进行分析。

回到顶部