商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 在.NET中使用SIMD的操作方法

在.NET中使用SIMD的操作方法

  发布于2026-07-23 阅读(0)

扫一扫,手机访问

SIMD(Single Instruction, Multiple Data)——单指令多数据,听起来像是个很硬核的概念,但其实它就在我们身边。现代CPU几乎都内置了SIMD指令集,比如Intel的SSE、A VX,ARM的NEON。简单说,就是让一条指令同时处理多个数据,而不是挨个儿来。这就像你有一排灯泡,传统做法是一个一个拧,SIMD则是整排一起亮。那么,在.NET里怎么用上这个能力?性能又能提升多少?下面从最基础的概念讲起,逐步深入到实际代码。

什么是 SIMD

SIMD(Single Instruction, Multiple Data) 译为 单指令多数据,是一种并行计算技术,允许单条指令同时对多个数据元素进行操作,从而提高计算效率。

与 SIMD 相对的是 SISD(Single Instruction, Single Data,单指令单数据),即每条指令只处理一个数据元素。

现在的大多数 CPU 都支持 SIMD 指令集,例如 Intel 的 SSE 和 A VX,ARM 的 NEON 等。

如果我们要对两组数组进行加法运算,传统方法(SISD)是逐个元素相加,而使用 SIMD 技术,可以一次性将多个元素加载到向量寄存器中,并执行单一的加法指令,从而显著提高计算效率。

在.NET中使用SIMD的操作方法

下面我们通过一个简单的示例,对比传统的数组加法和使用 SIMD 优化后的数组加法在性能上的差异。例子中会对两个浮点数组进行加法运算,把结果存储在第三个数组中。

using System.Runtime.Intrinsics;using BenchmarkDotNet.Attributes;using BenchmarkDotNet.Running;[MemoryDiagnoser]public class SimdBenchmark{    private float[] _arrA;    private float[] _arrB;    private float[] _resultArray;    private readonly int _dataSize = 1_000_000;    [GlobalSetup]    public void Setup()    {        var random = new Random();        _arrA = new float[_dataSize];        _arrB = new float[_dataSize];        _resultArray = new float[_dataSize];        for (int i = 0; i < _dataSize; i++)        {            _arrA[i] = (float)random.NextDouble() * 10f;            _arrB[i] = (float)random.NextDouble() * 10f;        }    }    [Benchmark]    public void NormalAdd()    {        for (int i = 0; i < _dataSize; i++)        {            _resultArray[i] = _arrA[i] + _arrB[i];        }    }    [Benchmark]    public void SimdAdd()    {        // 每次处理 4 个元素        int simdLength = Vector128.Count; // 4        int i = 0;        // 处理可被 SIMD 整除的部分        for (; i <= _dataSize - simdLength; i += simdLength)        {            var va = Vector128.Create(_arrA[i], _arrA[i + 1], _arrA[i + 2], _arrA[i + 3]);            var vb = Vector128.Create(_arrB[i], _arrB[i + 1], _arrB[i + 2], _arrB[i + 3]);            (va + vb).CopyTo(_resultArray, i);        }        // 处理尾部不足 4 个的元素        for (; i < _dataSize; i++)        {            _resultArray[i] = _arrA[i] + _arrB[i];        }    }}public class Program{    public static void Main(string[] args)    {        BenchmarkRunner.Run();    }}
BenchmarkDotNet v0.15.6, macOS Sequoia 15.7.2 (24G325) [Darwin 24.6.0]Apple M2 Max, 1 CPU, 12 logical and 12 physical cores.NET SDK 9.0.100  [Host]     : .NET 9.0.0 (9.0.0, 9.0.24.52809), Arm64 RyuJIT armv8.0-a  DefaultJob : .NET 9.0.0 (9.0.0, 9.0.24.52809), Arm64 RyuJIT armv8.0-a| Method    | Mean     | Error   | StdDev  | Allocated ||---------- |---------:|--------:|--------:|----------:|| NormalAdd | 880.4 us | 9.28 us | 7.75 us |         - || SimdAdd   | 568.5 us | 4.18 us | 3.70 us |         - |

在 MacBook Pro M2 Max 上测试,使用 SIMD 优化后的数组加法运算相比传统方法提升了约 35% 的性能。需要说明的是,这个例子中结果需要拷贝到目标数组,如果能在向量上直接做更多计算,性能提升会更明显。代码在 Windows 和 Linux 上同样可以跑,读者不妨自己测测不同平台的差异。

SIMD 基础 API

System.Runtime.Intrinsics 命名空间

.NET 提供了三个命名空间来使用 SIMD:

  • System.Runtime.Intrinsics:包含创建和传递各种大小寄存器状态的类型。
  • System.Runtime.Intrinsics.X86:x86/x64 架构特有的 SIMD 指令集。
  • System.Runtime.Intrinsics.Arm:ARM 架构特有的 SIMD 指令集。

System.Runtime.Intrinsics 中定义了表示不同大小向量的结构体,以及相应的静态操作类。

结构体

类型描述
Vector6464 位向量,用于低级别并行优化。
Vector128128 位向量,最常用的尺寸。
Vector256256 位向量。
Vector512512 位向量。

静态类

类型描述
Vector6464 位向量的创建与操作。
Vector128128 位向量的创建与操作。
Vector256256 位向量的创建与操作。
Vector512512 位向量的创建与操作。

System.Runtime.Intrinsics.X86System.Runtime.Intrinsics.Arm 则提供了对应硬件指令集的封装,比如 SseA vxAdvSimd 等。下面列出几个常见的类:

类型描述
Ssex86/x64 SSE 指令集
Sse2SSE2 指令集
A vxA VX 指令集
A vx2A VX2 指令集
AdvSimdARM Advanced SIMD (NEON)

更完整的信息可以查阅官方文档:System.Runtime.Intrinsics.X86 命名空间System.Runtime.Intrinsics.Arm 命名空间

如何理解向量的大小

向量的“大小”指的是寄存器能容纳的总位数。比如 128 位的向量,可以存 4 个 32 位浮点数(128/32=4),也可以存 2 个 64 位双精度浮点数(128/64=2)。很好理解:

using System.Runtime.Intrinsics;// 128位向量,16个字节Vector128 vectorByte = Vector128.Create((byte)1, (byte)2, (byte)3, (byte)4,                                               (byte)5, (byte)6, (byte)7, (byte)8,                                               (byte)9, (byte)10, (byte)11, (byte)12,                                               (byte)13, (byte)14, (byte)15, (byte)16);// 128位向量,4个浮点数Vector128 vectorFloat = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);// 256位向量,8个浮点数Vector256 vector256Float = Vector256.Create(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);// 128位向量,2个双精度浮点数Vector128 vectorDouble = Vector128.Create(1.0, 2.0);// 256位向量,4个双精度浮点数Vector256 vector256Double = Vector256.Create(1.0, 2.0, 3.0, 4.0);

跨平台实现方式

.NET 的 SIMD 是跨平台的。无论是 x86 还是 ARM,运行时都会自动选择最合适的硬件指令集来执行。你可以用 VectorXXX.IsHardwareAccelerated 检查当前平台是否支持特定大小的向量操作:

Console.WriteLine(Vector128.IsHardwareAccelerated ? "128 位向量操作受支持" : "128 位向量操作不受支持");

但即便硬件不支持,.NET 也会自动回退到非 SIMD 的实现,保证代码兼容性。

向量运算既可以用静态方法如 Vector128.Add,也可以直接用 +-*/ 等运算符——结构体重载了这些符号,写起来很直观:

using System.Runtime.Intrinsics;Vector128 vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);Vector128 vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);var result = Vector128.Add(vectorA, vectorB);Console.WriteLine($"Result: {result}");
Result: <6, 8, 10, 12>

SIMD 指令集的使用

使用特定指令集之前,通常需要检查平台是否支持,通过 IsSupported 属性来判断:

using System.Runtime.Intrinsics.X86;Console.WriteLine(Sse.IsSupported ? "SSE 指令集受支持" : "SSE 指令集不受支持");

确认支持后,就可以调用指令集类提供的静态方法了。例如用 Sse.Add 做加法:

using System.Runtime.Intrinsics;using System.Runtime.Intrinsics.X86;if (Sse.IsSupported){    Vector128 vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);    Vector128 vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);    var result = Sse.Add(vectorA, vectorB);    Console.WriteLine($"Result: {result}");}else{    Console.WriteLine("SSE 指令集不受支持");}
Result: <6, 8, 10, 12>

ARM 平台则用 AdvSimd

using System.Runtime.Intrinsics;using System.Runtime.Intrinsics.Arm;if (AdvSimd.IsSupported){    Vector128 vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);    Vector128 vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);    var result = AdvSimd.Add(vectorA, vectorB);    Console.WriteLine($"Result: {result}");}else{    Console.WriteLine("AdvSimd 指令集不受支持");}
Result: <6, 8, 10, 12>

System.Numerics 命名空间中的 SIMD 支持

除了底层 Intrinsics,.NET 在 System.Numerics 里提供了更高级的封装,比如 VectorMatrix4x4,让 SIMD 编程更简单。

Vector 结构体

Vector 是一个通用向量类型,支持多种数值类型,并且会自动根据硬件选择最佳向量大小(128位或256位),真正做到跨平台优化。下面是用它做数组加法的例子:

var vectorSize = Vector.Count;float[] arrayA = new float[1000];float[] arrayB = new float[1000];float[] resultArray = new float[1000];for (int i = 0; i <= arrayA.Length - vectorSize; i += vectorSize){    var va = new Vector(arrayA, i);    var vb = new Vector(arrayB, i);    (va + vb).CopyTo(resultArray, i);}

Vector2、Vector3 和 Vector4

这些结构体分别表示二维、三维、四维向量,常用于图形和物理计算。内部实现已经用上了 SIMD:

using System.Numerics;Vector3 vector1 = new Vector3(1.0f, 2.0f, 3.0f);Vector3 vector2 = new Vector3(4.0f, 5.0f, 6.0f);Vector3 resultAdd = Vector3.Add(vector1, vector2);Console.WriteLine($"Addition: {resultAdd}"); // <5, 7, 9>float dotProduct = Vector3.Dot(vector1, vector2);Console.WriteLine($"Dot Product: {dotProduct}"); // 32Vector3 normalized = Vector3.Normalize(vector1);Console.WriteLine($"Normalized: {normalized}"); // <0.2672612, 0.5345225, 0.8017837>

打开 Vector3 的源码看看,会发现它内部就是用 Vector128.Create 实现的:

public struct Vector3 : IEquatable, IFormattable{    public float X;    public float Y;    public float Z;    public Vector3(float x, float y, float z) => this = Vector3.Create(x, y, z);    public static Vector3 Create(float x, float y, float z)    {        return Vector128.Create(x, y, z, 0.0f).AsVector3();    }    // 其他成员...}

Matrix2x2、Matrix3x2 和 Matrix4x4

这些矩阵结构体同样内置了 SIMD 优化,用于变换和投影:

using System.Numerics;Matrix4x4 matrix = Matrix4x4.CreateRotationX((float)(Math.PI / 4));Vector3 point = new Vector3(1.0f, 0.0f, 0.0f);Vector3 transformedPoint = Vector3.Transform(point, matrix);Console.WriteLine($"Transformed Point: {transformedPoint}");
Transformed Point: <1, 0, 0>

其他 SIMD 的使用场景举例

字母大小写转换

ASCII 码表中,大小写字母的差异仅在第 6 位(值为 0x20)。大写字母该位为 0,小写为 1。因此用异或操作就能轻松转换:

Console.WriteLine((char)('a' ^ 0x20)); // 输出 AConsole.WriteLine((char)('A' ^ 0x20)); // 输出 a

查看 ASCII 表对照:

字符十进制十六进制二进制
A650x4101000001
B660x4201000010
C670x4301000011
D680x4401000100
a970x6101100001
b980x6201100010
c990x6301100011
d1000x6401100100

.NET 的 System.Text.AsciiToUpper、ToLower 等方法,底层就是利用 SIMD 加速的。下面是一个简化版的核心实现,展示了如何用向量判断小写字母范围并转换:

void ToUpperInPlace(Span value){    var buffer = MemoryMarshal.Cast(value);    var elementCount = (uint)buffer.Length;    var numElementsPerVector = (uint)(Unsafe.SizeOf>() / sizeof(ushort));    if (Vector128.IsHardwareAccelerated && elementCount >= numElementsPerVector)    {        ushort sourceSignedMinValue = (ushort)(1 << (8 * sizeof(ushort) - 1));        var subtractionVector = Vector128.Create((ushort)(sourceSignedMinValue + 'a'));        var comparisonVector = Vector128.Create((ushort)(sourceSignedMinValue + 26));        var caseConversionVector = Vector128.Create((ushort)0x20);        uint i = 0;        uint n = elementCount - (elementCount % numElementsPerVector);        for (; i < n; i += numElementsPerVector)        {            var srcVector = Vector128.LoadUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i));            var matches = SignedLessThan(srcVector - subtractionVector, comparisonVector);            srcVector ^= matches & caseConversionVector;            srcVector.StoreUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i));        }        for (; i < elementCount; i++)        {            ushort c = Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i);            if (c is >= 'a' and <= 'z')            {                c = (ushort)(c - 0x20);            }            Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i) = c;        }    }    else    {        for (int i = 0; i < buffer.Length; i++)        {            ushort c = buffer[i];            if (c is >= 'a' and <= 'z')            {                c = (ushort)(c - 0x20);            }            buffer[i] = c;        }    }}Vector128 SignedLessThan(Vector128 left, Vector128 right){    return Vector128.LessThan(left.AsInt16(), right.AsInt16()).AsInt16().AsUInt16();}

调用示例:

string input = "Hello World! This is a Test String.";Span p = input.ToCharArray();ToUpperInPlace(p);string result = new string(p);Console.WriteLine(result); // "HELLO WORLD! THIS IS A TEST STRING."

需要说明的是,实际 Ascii 类要复杂得多,包含边界检查和错误处理,上述代码只是核心思路的简化版。日常使用直接调 Ascii.ToUpperInPlace 即可:

using System.Text;string input = "Hello World! This is a Test String. 这部分不会被转换。";Span p = input.ToCharArray();Ascii.ToUpperInPlace(p, out int charsWritten);string result = new string(p[..charsWritten]);Console.WriteLine(result); // "HELLO WORLD! THIS IS A TEST STRING."

二进制/位操作

SIMD 也适合大批量二进制数据的位操作。比如 BinaryPrimitives.ReverseEndianness 就利用 SIMD 高效反转字节序:

Span data = [0x1234, 0xABCD, 0x5678, 0xEF01];Span reversedData = stackalloc ushort[4];BinaryPrimitives.ReverseEndianness(data, reversedData);foreach (var value in reversedData){    Console.WriteLine(value.ToString("X4"));}
3412CDAB785601EF

总结

SIMD 在 .NET 中提供了强大的并行计算能力,能显著提升大数据量处理的性能。通过 System.Runtime.IntrinsicsSystem.Numerics,我们可以方便地利用 SIMD 指令进行高效的向量化运算。很多基础库已经内置了 SIMD 优化,日常开发中通过它们就能间接受益,无需深入了解底层细节。当然,对于性能敏感的应用,直接使用 SIMD 指令集仍然是非常值得掌握的技能。

本文转载于:https://www.jb51.net/aspnet/3528803nd.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注