跨平台独立SIMD在Go中
Go 1.26和1.27包含用于单指令多数据(SIMD)操作的实验性API。SIMD是许多现代CPU的本机特性,允许软件在数据向量上快速执行统一操作,例如在单个指令中添加8对float64值。它可以显著加速许多计算密集型任务,从密码学到数据处理,再到人工智能。实际上,Go的Green Tea垃圾回收器甚至利用SIMD加速内存中活动对象的扫描。在这些新的实验性API之前,从Go访问此功能的唯一方法是编写Go汇编。这仅对于真正的性能关键计算内核值得付出,这意味着许多可以受益于SIMD的软件根本没能充分利用CPU。Go 1.26引入了amd64的SIMD API,而Go 1.27增加了arm64(特别是NEON)和wasm的API。然而,SIMD API的一个基本挑战是平台之间的巨大差异,不仅在于它们支持的操作,还在于向量的表示方式。一些平台提供固定大小的向量,通常介于128位和512位之间,而在其他平台上,向量大小在构建时并不知道,必须在程序启动时查询。为了提供对这些平台广泛访问,这些API位于依赖于架构的archsimd包中。然而,Go 1.27超越了这些依赖于架构的API,并引入了一个实验性的、完全可移植的、与平台和大小无关的SIMD接口,该接口大致基于C++的Highway。目标是在具有SIMD支持的平台上支持一次编写接近汇编性能的“simd”代码,并在尚未(或尚未)具有SIMD支持的平台上提供合适的仿真。当前,simd包支持amd64上的AVX、AVX2和AVX512、arm64上的NEON以及wasm的SIMD指令。动机:SIMD架构之间的变异 SIMD架构在几个维度上有所不同。一些提供单个固定向量大小(wasm、PowerPC和s390x,128位)。一些提供几个固定向量大小(amd64,128、256和512;loong64,128和256)。riscv64支持介于128和65536位之间的未指定大小的向量,不过长度限制为2的幂。Arm64支持一个固定大小(128位,NEON)和一个可变大小(128-2048位,仅为2的幂,SVE)。在特定架构的给定实例中,确定该实例支持哪些大小需要特性检查:amd64,但它是AVX、AVX2还是AVX512?Arm64,但它是NEON还是SVE?如果是SVE,大小多少?哪种SVE变体:SVE、SVE2或SVE2.1?不同的SIMD架构在处理向量掩码的方式上有所不同。对于向量,可以使用掩码实现向量上的如果-那么-否则;进行操作,但仅在掩码为“真”的地方分配结果(或加载或存储)。一些SIMD变体不提供掩码;所有操作在所有元素上执行,“掩码”是使用向量位掩码和向量布尔操作完成的(wasm、AVX、AVX2、NEON)。一些提供特殊的掩码寄存器,一个比特控制一向量元素上的操作(AVX512和RVV)。其他(SVE)分配一个比特每个向量字节,但每个元素的掩码位的最低有效比特控制掩码操作。AVX2也支持掩码加载和存储,但使用普通向量作为掩码,并且由最高有效比特控制操作。第三个变异来源是在操作本身。每个架构提供自己重新排列向量元素的原语;一些需要常量输入,另一些支持可变输入。不同的SIMD架构支持不同的与加密相关的操作。即使是基本的算术操作也会有不同的支持;例如,wasm缺少对64位整数向量的比较。即使在特定架构的给定向量长度上,指令支持也取决于必须检查的“特性”。尽管Go的依赖于架构的archsimd包旨在尽可能在跨架构之间保持一致,但许多这些特性仍然存在,导致为多平台SIMD设计、编写和测试代码变得繁重。我们可以在archsimd包中做更多工作,以使不同的架构显得更相似,但在不妥协效率的前提下,我们只能做到这一点。概述 新的simd包通过从类型系统中删除固定大小的向量以及仅支持所有不同平台交集中的那些操作来隐藏这些差异,并用其他SIMD指令的有效仿真填补交集中的空白。目标是一组足以支持许多受益于向量化实现的数据处理算法(但不局限于特定向量大小)、在源代码操作与底层硬件匹配时与汇编语言一样高效的操作,在其他情况下尽可能提供良好的仿真。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡