写点什么

在 Go 中使用 Neon

作者:geange
  • 2023-08-01
    广东
  • 本文字数:1615 字

    阅读完需:约 5 分钟

在Go中使用Neon

由于大模型的爆火,SIMD 正在受到越来越多的关注

https://www.yuque.com/chenhualin/go/kxzyynwculu2pm4n

什么是 Neon


Arm Neon 技术是面向 A/R 型处理器的先进单指令多数据(SIMD)架构扩展。


A 系列处理器面向高性能计算设备,支持 32 位和 64 位软件;而 R 系列处理器则专注于实时和深度嵌入式系统,注重确定性和低延迟响应时间。这两个配置都属于 ARM 架构,并为不同的应用领域提供支持。


Neon 技术是一种打包的 SIMD 架构。Neon 寄存器被视为具有相同数据类型的元素的向量,Neon 指令可以同时对多个元素进行操作。该技术支持多种数据类型,包括浮点和整数运算。Neon 技术在 ARM 处理器中广泛应用于加速多媒体和信号处理等计算密集型任务,提高了移动设备和嵌入式系统的性能和效率。

如何在 Go 中使用 Neon

cgo

Github 已经有一些库已经封装好了对 arm 的调用



C 语言的函数调用是通过寄存器,而 Go 的函数调用主要是通过栈调用。如果你对如果使用 c 语言和 arm 汇编感兴趣,可以看下


使用汇编调用

在 Golang 中,调用机器指令并不是一个简单的事情:


  • Go Asm 缺少官方规范

  • 处理器架构差异,寄存器的名称数量,使用方式上都有较大的差异


本文默认读者已经掌握一定的Go AsmArm Asm的相关知识。下面知识是可以用于简单回顾下 Go Asm 汇编的知识。



这里不会展开过多,只是提供一个简单的例子,【2 个字节数组的相加】。


当前我们有 2 个字节数组,每个数组的长度为 8a1 := []byte{0, 1, 2, 3, 4, 5, 6, 7}a2 := []byte{1, 1, 1, 1, 1, 1, 1, 1}我们需要将相同序号的数字进行相加,获得一个他们的和的数组,不需要考虑溢出。


通常情况下我们会使用 for 对数据进行处理,但如果是使用 Neon,只需要执行一次相加处理即可(不考虑内存拷贝等操作)。

汇编文件

新建一个 asm.s 文件


TEXT ·_SIMD_ADD_Byte8(SB), $0-24  // 输入为2个uint64,输出为1个uint64    MOVD arg1+0(FP), R1    // 将第1个入参放到R1寄存器中    MOVD arg2+8(FP), R2    // 将第2个入参放到R2寄存器中    VMOV R1, V1.D[0]    // 将R1的数据拷贝到V1.D[0]    VMOV R2, V2.D[0]    // 将R2的数据拷贝到V2.D[0]    VADD V1.H8, V2.H8, V3.H8  // V1、V2寄存器每8位进行加法操作                                // H8代表下图中的H0                                // V1.D[0]代表D1                                // V2.D[0]代表D2    VMOV V3.D[0], R1      // 将V3.D[0]的数字拷贝到R1    MOVD R1, result+16(FP)    // 将R1拷贝到栈上    RET
复制代码



Arm 中有 32 个向量寄存器,每个寄存器为 128-bit。在 Arm 汇编中,我们使用 V1 代表一个 128-bit 的寄存器,D0 代表 V1 低 64 位寄存器,D1 代表高 64 位寄存器。


如果你想要相加的数据为 uint16(2 个 byte),即


a1 := []byte{0, 1, 2, 3}a2 := []byte{1, 1, 1, 1}


VADD V1.H4, V2.H4, V3.H4  // 计算4个uint16
复制代码

Go 函数

在 asm.s 的目录下,新建一个 asm.go


package main
func _SIMD_ADD_Byte8(uint64, uint64) uint64
复制代码


然后在 main.go 中调用_SIMD_ADD_Byte8


这里我们把一个 8 个元素的字节当作一个 uint64 数字,是为了让例子更简单


package main
func main() { a1 := uint64(0x0001020304050607) // []byte{0, 1, 2, 3, 4, 5, 6, 7} a2 := uint64(0x0101010101010101) // []byte{1, 1, 1, 1, 1, 1, 1, 1} sum := _SIMD_ADD_Byte8(a1, a2) fmt.Printf("0x%X", sum)}
复制代码


// go run main.go0x102030405060708
复制代码

注意点

  • 不能直接从栈拷贝到向量寄存器/从向量寄存器拷贝到栈,暂时还没找到原因。我猜测跟 FP 是 Go Asm 的虚拟寄存器有关系,希望有大佬解答下。


MOVD arg1+0(FP), V1 // 不合法MOVD V3.D[0], result+16(FP) // 不合法
复制代码


  • Go Asm 使用 MOV 指令来做数据加载操作,跟 arm 汇编有较大的差距,需要留意他们的映射规则


发布于: 刚刚阅读数: 4
用户头像

geange

关注

还未添加个人签名 2018-05-26 加入

还未添加个人简介

评论

发布
暂无评论
在Go中使用Neon_Go_geange_InfoQ写作社区