专栏名称: GoCN
最具规模和生命力的 Go 开发者社区
目录
相关文章推荐
阿拉善宏桥信息网  ·  2025.02.23更新【5】家政服务、生活 ... ·  7 小时前  
素食星球  ·  椰香四溢的纯素雪花酥,颠覆传统的味蕾体验 ·  14 小时前  
素食星球  ·  椰香四溢的纯素雪花酥,颠覆传统的味蕾体验 ·  14 小时前  
赛博禅心  ·  超级硬广:输入 ikun,解锁 ... ·  昨天  
赛博禅心  ·  超级硬广:输入 ikun,解锁 ... ·  昨天  
51好读  ›  专栏  ›  GoCN

啥时候等到Go官方支持SIMD? 不等也行!

GoCN  · 公众号  ·  · 2025-02-06 08:00

正文

单指令多数据流( SIMD ,Single Instruction Multiple Data)是一种并行计算技术,允许一条指令同时处理多个数据点。SIMD 在现代 CPU 中广泛应用,能够显著提升计算密集型任务的性能,如图像处理、机器学习、科学计算等。随着 Go 语言在高性能计算领域的应用逐渐增多,SIMD 支持成为了开发者关注的焦点。


当前很多主流和新型的语言都有相应的 simd 库了,比如 C++、Rust、Zig 等,但 Go 语言的 simd 官方支持还一直在讨论中( issue#67520 [1] )。Go 语言的设计目标是简单性和可移植性,而 SIMD 的实现通常需要针对不同的硬件架构进行优化,这与 Go 的设计目标存在一定冲突。因此,Go 语言对 SIMD 的支持一直备受争议。最近几周这个 issue 的讨论有活跃起来, 希望能快点支持。

1. Go 语言与 SIMD 的背景

1.1 Go 语言的性能追求

Go 语言以其简洁的语法、高效的并发模型和快速的编译速度赢得了广泛的应用。然而,Go 在性能优化方面一直面临挑战,尤其是在需要处理大量数据的场景下。SIMD 作为一种高效的并行计算技术,能够显著提升计算性能,因此 Go 社区对 SIMD 的支持呼声日益高涨。

如果没有 SIMD,我们就会错过很多潜在的优化。以下是可以提高日常生活场景中性能的具体事项的非详尽列表:

  • simdjson [2]
  • 通过矢量化每秒解码数十亿个整数 [3]
  • 矢量化和性能可移植的快速排序 [4]
  • Hyperscan 简介 [5]
  • From slow to SIMD: A Go optimization story [6]
  • How to Use AVX512 in Golang via C Compiler [7]

此外,它将使这些当前存在的软件包更具可移植性和可维护性:

  • simdjson-go [8]
  • SHA256-SIMD [9]
  • MD5-SIMD [10]

在这个月即将发布的 Go 1.24 版中,将会将内建的 map 使用 Swiss Tables 替换,而 Swiss Tables 针对 AMD64 的架构采用了 SIMD 的代码 [11] ,这是不是 Go 官方代码库首次引进了 SIMD 的指令呢?

当前先前也有人实现了 SIMD 加速 encoding/hex, 被否了 [12] ,当然理由也很充分:加速效果很好但请放弃吧,看起来太复杂,违背了 Go 简洁的初衷。类似的还有 unicode/utf8: make Valid use AVX2 on amd64 [13]

其实 Go 官方在 2023 就已经在标准库 crypto/sha256 中使用 SIMD 指令了 crypto/sha256: add sha-ni implementation [14]

1.2 SIMD 的基本概念

SIMD 通过一条指令同时处理多个数据点,通常用于向量化计算。现代 CPU(如 Intel 的 SSE/AVX 、ARM 的 NEON )都提供了 SIMD 指令集,允许开发者通过特定的指令集加速计算任务。然而,直接使用 SIMD 指令集通常需要编写汇编代码或使用特定的编译器内置函数,这对开发者提出了较高的要求。

1.2.1 SIMD 的核心思想

SIMD 的核心思想是通过一条指令同时处理多个数据点。例如,传统的标量加法指令一次只能处理两个数,而 SIMD 加法指令可以同时处理多个数(如 4 个、8 个甚至更多)。这种并行化处理方式能够显著提升计算密集型任务的性能。

1.2.2 SIMD 指令集的组成

SIMD 指令集通常包括以下几类指令:

  • 算术运算 :加法、减法、乘法、除法等。
  • 逻辑运算 :与、或、非、异或等。
  • 数据搬移 :加载、存储、重排数据。
  • 比较操作 :比较多个数据点并生成掩码。
  • 特殊操作 :如求平方根、绝对值、最大值、最小值等。

1.3 常见的指令集

1.3.1 Intel 的 SIMD 指令集

1.3.1.1 MMX(MultiMedia eXtensions)
  • 推出时间 :1996 年
  • 寄存器宽度 :64 位
  • 数据类型 :整数(8 位、16 位、32 位)
  • 特点
    • 主要用于多媒体处理。
    • 引入了 8 个 64 位寄存器(MM0-MM7)。
    • 不支持浮点数运算。
1.3.1.2 SSE(Streaming SIMD Extensions)
  • 推出时间 :1999 年
  • 寄存器宽度 :128 位
  • 数据类型 :单精度浮点数(32 位)、整数(8 位、16 位、32 位、64 位)
  • 特点
    • 引入了 8 个 128 位寄存器(XMM0-XMM7)。
    • 支持浮点数运算,适用于科学计算和图形处理。
    • 后续版本(SSE2、SSE3、SSSE3、SSE4)增加了更多指令和功能。
1.3.1.3 AVX(Advanced Vector Extensions)
  • 推出时间 :2011 年
  • 寄存器宽度 :256 位
  • 数据类型 :单精度浮点数(32 位)、双精度浮点数(64 位)、整数(8 位、16 位、32 位、64 位)
  • 特点
    • 引入了 16 个 256 位寄存器(YMM0-YMM15)。
    • 支持更宽的向量操作,性能进一步提升。
    • 后续版本(AVX2、AVX-512)支持更复杂的操作和更宽的寄存器(512 位)。
1.3.1.4 AVX-512
  • 推出时间 :2016 年
  • 寄存器宽度 :512 位
  • 数据类型 :单精度浮点数(32 位)、双精度浮点数(64 位)、整数(8 位、16 位、32 位、64 位)
  • 特点
    • 引入了 32 个 512 位寄存器(ZMM0-ZMM31)。
    • 支持更复杂的操作,如掩码操作、广播操作等。
    • 主要用于高性能计算和人工智能领域。

1.3.2 ARM 的 SIMD 指令集

1.3.2.1 NEON
  • 推出时间 :2005 年
  • 寄存器宽度 :128 位
  • 数据类型 :单精度浮点数(32 位)、整数(8 位、16 位、32 位、64 位)
  • 特点
    • 广泛应用于移动设备和嵌入式系统。
    • 支持 16 个 128 位寄存器(Q0-Q15)。
    • 适用于多媒体处理、信号处理等场景。
1.3.2.2 SVE(Scalable Vector Extension)
  • 推出时间 :2016 年
  • 寄存器宽度 :可变(128 位至 2048 位)
  • 数据类型 :单精度浮点数(32 位)、双精度浮点数(64 位)、整数(8 位、16 位、32 位、64 位)
  • 特点
    • 支持可变长度的向量操作,适应不同的硬件配置。
    • 引入了谓词寄存器(Predicate Registers),支持条件执行。
    • 主要用于高性能计算和机器学习。

1.4 编译器内置函数

大多数现代编译器(如 GCC、Clang、MSVC)提供了 SIMD 指令集的内置函数,开发者可以通过这些函数调用 SIMD 指令,而无需编写汇编代码。

1.5 自动向量化

一些编译器支持自动向量化功能,能够自动将标量代码转换为 SIMD 代码。例如,使用 GCC 编译以下代码时,可以启用自动向量化:

gcc -O3 -mavx2 -o program program.c

2. Go 语言中的 SIMD 支持现状

2.1 Go 语言标准库的 SIMD 支持

Go 语言的标准库尚未提供对 SIMD 的直接支持。Go 语言的编译器(gc)也没有自动向量化功能,这意味着开发者无法像在 C/C++中那样通过编译器自动生成 SIMD 代码。

在 Issue #67520 [15] 中,讨论依然磨磨唧唧,讨论时常偏离到实现的具体方式上(build tag)。

2.2 第三方库与解决方案

尽管 Go 语言标准库缺乏对 SIMD 的直接支持,但社区已经开发了一些第三方库和工具,帮助开发者在 Go 中使用 SIMD 指令集。在 #67520 [16] 的讨论中,Clement Jean 也提供了一个概念化的实现方案: simd-go-POC [17]

以下是一些第三方实现的(simd 指令,不是基于 simd 实现的库 sonic、simdjson-go 等):

2.2.1 kelindar/simd

kelindar/simd [18] 这个库包含一组矢量化的数学函数,它们使用 clang 编译器自动矢量化,并转换为 Go 的 PLAN9 汇编代码。对于不支持矢量化的 CPU,或此库没有为其生成代码的 CPU,也提供了通用版本。

目前它仅支持 AVX2,但生成 AVX512 和 SVE (for ARM) 的代码应该很容易。这个库中的大部分代码都是自动生成的,这有助于维护。

sum := simd.SumFloat32s([]float32{12345})

2.2.2 alivanz/go-simd

[alivanz/go-simd](https://github.com/alivanz/go-simd)实现了 Go 语言的 SIMD(单指令多数据)操作,专门针对 ARM NEON 架构进行了优化。其目标是为特定的计算任务提供优化的并行处理能力。下面是一个加法和乘法的例子:

package main

import (
"log"

"github.com/alivanz/go-simd/arm"
"github.com/alivanz/go-simd/arm/neon"
)

func main() {
var a, b arm.Int8X8
var add, mul arm.Int16X8
for i := 0; i < 8; i++ {
  a[i] = arm.Int8(i)
  b[i] = arm.Int8(i * i)
 }
 log.Printf("a = %+v", b)
 log.Printf("b = %+v", a)
 neon.VaddlS8(&add, &a, &b)
 neon.VmullS8(&mul, &a, &b)
 log.Printf("add = %+v", add)
 log.Printf("mul = %+v", mul)
}

2.2.3 pehringer/simd

pehringer/simd [19] 通过 Go 汇编提供 SIMD 支持,实现了算术运算、位运算以及最大值和最小值运算。它允许进行并行的逐元素计算,从而带来 100% 到 400% 的速度提升。目前支持 AMD64 (x86_64) 和 ARM64 处理器。

2.3 Go 汇编与 SIMD

Go 语言支持通过汇编代码直接调用 CPU 指令集,这为 SIMD 的实现提供了可能。开发者可以编写 Go 汇编代码,调用特定的 SIMD 指令集(如 SSE、AVX 等),从而实现高性能的向量化计算。然而,编写和维护汇编代码对开发者提出了较高的要求,且代码的可移植性较差。

// 以下是一个简单的Go汇编示例,使用AVX指令集进行向量加法
TEXT ·add(SB), $0-32
    MOVQ a+0(FP), DI
    MOVQ b+8(FP), SI
    MOVQ result+16(FP), DX
    MOVQ len+24(FP), CX

    TESTQ CX, CX        ; 检查长度是否为0
    JZ done             ; 如果为0直接返回

    MOVQ CX, R8         ; 保存原始长度
    SHRQ $2, CX         ; 除以4得到循环次数
    JZ remainder        ; 如果不足4个元素,跳到处理余数

    XORQ R9, R9         ; 用于索引的计数器,从0开始
loop:
    VMOVUPD (DI)(R9*8), Y0
    VMOVUPD (SI)(R9*8), Y1
    VADDPD Y0, Y1, Y0
    VMOVUPD Y0, (DX)(R9*8)
    ADDQ $4, R9
    DECQ CX
    JNZ loop

remainder:              ; 处理剩余的元素
    ANDQ $3, R8        ; 获取余数
    JZ done
    ; 这里添加处理余数的代码

done:
    RET

当然需要 a,b 和 result 数组的地址是对齐的,以获得最佳性能。

结论

尽管 Go 语言目前对 SIMD 的支持尚不完善,但社区已经通过第三方库和汇编代码提供了一些解决方案。未来,随着 Go 编译器的改进和标准库的支持(相信 Go 官方最终会支持的),Go 语言在高性能计算领域的潜力将进一步释放。对于开发者而言,掌握 SIMD 技术将有助于编写更高效的 Go 代码,应对日益复杂的计算任务。

参考资料
[1]

issue#67520: https://github.com/golang/go/issues/67520

[2]

simdjson: https://github.com/simdjson/simdjson

[3]

通过矢量化每秒解码数十亿个整数: https://people.csail.mit.edu/jshun/6886-s19/lectures/lecture19-1.pdf







请到「今天看啥」查看全文