在 .NET 中使用 SIMD的操作方法
什么是 SIMD
SIMD(Single Instruction, Multiple Data) 譯為 單指令多數(shù)據(jù),是一種并行計(jì)算技術(shù),允許單條指令同時(shí)對(duì)多個(gè)數(shù)據(jù)元素進(jìn)行操作,從而提高計(jì)算效率。
與 SIMD 相對(duì)的是 SISD(Single Instruction, Single Data,單指令單數(shù)據(jù)),即每條指令只處理一個(gè)數(shù)據(jù)元素。
現(xiàn)在的大多數(shù) CPU 都支持 SIMD 指令集,例如 Intel 的 SSE 和 AVX,ARM 的 NEON 等。
如果我們要對(duì)兩組數(shù)組進(jìn)行加法運(yùn)算,傳統(tǒng)方法(SISD)是逐個(gè)元素相加,而使用 SIMD 技術(shù),可以一次性將多個(gè)元素加載到向量寄存器中,并執(zhí)行單一的加法指令,從而顯著提高計(jì)算效率。

下面我們通過一個(gè)簡(jiǎn)單的示例,對(duì)比傳統(tǒng)的數(shù)組加法和使用 SIMD 優(yōu)化后的數(shù)組加法在性能上的差異。例子中會(huì)對(duì)兩個(gè)浮點(diǎn)數(shù)組進(jìn)行加法運(yùn)算,把結(jié)果存儲(chǔ)在第三個(gè)數(shù)組中。
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 個(gè)元素
int simdLength = Vector128<float>.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 個(gè)的元素
for (; i < _dataSize; i++)
{
_resultArray[i] = _arrA[i] + _arrB[i];
}
}
}
public class Program
{
public static void Main(string[] args)
{
BenchmarkRunner.Run<SimdBenchmark>();
}
}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 上測(cè)試,使用 SIMD 優(yōu)化后的數(shù)組加法運(yùn)算相比傳統(tǒng)方法提升了約 35% 的性能。
此處所使用的例子可能會(huì)受到結(jié)果需要拷貝到結(jié)果數(shù)組的影響,實(shí)際應(yīng)用中如果能直接在向量上進(jìn)行更多計(jì)算,性能提升會(huì)更加顯著。
此例子也可以在 Windows 和 Linux 上運(yùn)行,有興趣的讀者可以自行測(cè)試不同平臺(tái)的性能差異。
SIMD 基礎(chǔ) API
System.Runtime.Intrinsics 命名空間
.NET 為我們提供了下面三個(gè)命名空間來使用 SIMD 技術(shù):
- System.Runtime.Intrinsics :包含用于創(chuàng)建和傳遞各種大小和格式的寄存器狀態(tài)的類型。
- System.Runtime.Intrinsics.X86 :包含特定于 x86/x64 架構(gòu)的 SIMD 指令集的類型。
- System.Runtime.Intrinsics.Arm :包含特定于 ARM 架構(gòu)的 SIMD 指令集的類型。
System.Runtime.Intrinsics 命名空間中定義了表示不同大小向量的結(jié)構(gòu)體和提供創(chuàng)建及操作這些向量的靜態(tài)類。
結(jié)構(gòu)體
| 類型 | 描述 |
|---|---|
| Vector64<T> | 表示指定數(shù)值類型的 64 位向量,該向量適用于并行算法的低級(jí)別優(yōu)化。 |
| Vector128<T> | 表示指定數(shù)值類型的 128 位向量,該向量適用于并行算法的低級(jí)別優(yōu)化。 |
| Vector256<T> | 表示指定數(shù)值類型的 256 位向量,該向量適用于并行算法的低級(jí)別優(yōu)化。 |
| Vector512<T> | 表示指定數(shù)值類型的 512 位向量,該向量適用于并行算法的低級(jí)別優(yōu)化。 |
靜態(tài)類
| 類型 | 描述 |
|---|---|
| Vector64 | 提供靜態(tài)方法的集合,用于在 64 位向量上創(chuàng)建、操作和以其他方式操作。 |
| Vector128 | 提供靜態(tài)方法集合,用于在 128 位向量上創(chuàng)建、操作和以其他方式操作。 |
| Vector256 | 提供靜態(tài)方法集合,用于在 256 位向量上創(chuàng)建、操作和以其他方式操作。 |
| Vector512 | 提供靜態(tài)方法的集合,用于在 512 位向量上創(chuàng)建、操作和以其他方式操作。 |
System.Runtime.Intrinsics.X86 和 System.Runtime.Intrinsics.Arm 命名空間中定義了特定于各自架構(gòu)的 SIMD 指令集的類,這些類提供了訪問底層硬件 SIMD 指令的能力。
常見的指令集類例如:
| 類型 | 描述 |
|---|---|
| Sse | 提供對(duì) x86/x64 SSE 指令集的訪問。 |
| Sse2 | 提供對(duì) x86/x64 SSE2 指令集的訪問。 |
| Avx | 提供對(duì) x86/x64 AVX 指令集的訪問。 |
| Avx2 | 提供對(duì) x86/x64 AVX2 指令集的訪問。 |
| AdvSimd | 提供對(duì) ARM Advanced SIMD(NEON)指令集的訪問。 |
更詳細(xì)的列表可以參考官方文檔:
System.Runtime.Intrinsics.X86 命名空間
System.Runtime.Intrinsics.Arm 命名空間
如何理解向量的大小
向量的大?。ㄈ?64 位、128 位、256 位、512 位)指的是向量寄存器能夠容納的數(shù)據(jù)總位數(shù)。每個(gè)向量寄存器可以存儲(chǔ)多個(gè)數(shù)據(jù)元素,這些數(shù)據(jù)元素的類型和數(shù)量取決于向量的大小和數(shù)據(jù)類型的位數(shù)。
例如開頭用到的 Vector128<float>,它表示一個(gè) 128 位的向量寄存器,可以存儲(chǔ) 4 個(gè) 32 位的浮點(diǎn)數(shù)(因?yàn)?128 / 32 = 4)。
如果是用來存儲(chǔ) 64 位的雙精度浮點(diǎn)數(shù)(double),則 Vector128<double> 可以存儲(chǔ) 2 個(gè)雙精度浮點(diǎn)數(shù)(因?yàn)?128 / 64 = 2)。
using System.Runtime.Intrinsics;
// 創(chuàng)建一個(gè) 128 位的向量,存儲(chǔ) 16 個(gè) 8 位的 字節(jié)
Vector128<byte> 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);
// 創(chuàng)建一個(gè) 128 位的向量,存儲(chǔ) 4 個(gè) 32 位的 浮點(diǎn)數(shù)
Vector128<float> vectorFloat = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
// 創(chuàng)建一個(gè) 256 位的向量,存儲(chǔ) 8 個(gè) 32 位的 浮點(diǎn)數(shù)
Vector256<float> vector256Float = Vector256.Create(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
// 創(chuàng)建一個(gè) 128 位的向量,存儲(chǔ) 2 個(gè) 64 位的 雙精度浮點(diǎn)數(shù)
Vector128<double> vectorDouble = Vector128.Create(1.0, 2.0);
// 創(chuàng)建一個(gè) 256 位的向量,存儲(chǔ) 4 個(gè) 64 位的 雙精度浮點(diǎn)數(shù)
Vector256<double> vector256Double = Vector256.Create(1.0, 2.0, 3.0, 4.0);跨平臺(tái)實(shí)現(xiàn)方式
.NET 的 SIMD 提供了跨平臺(tái)的實(shí)現(xiàn)方式。無論是在 x86/x64 還是 ARM 架構(gòu)上,.NET 都會(huì)根據(jù)運(yùn)行時(shí)環(huán)境自動(dòng)選擇合適的 SIMD 指令集來執(zhí)行向量化操作。
我們可以使用 VectorXXX.IsHardwareAccelerated 屬性來檢查當(dāng)前平臺(tái)是否支持特定大小的向量操作。例如:
Console.WriteLine(Vector128.IsHardwareAccelerated ? "128 位向量操作受支持" : "128 位向量操作不受支持");
但即使硬件不支持 SIMD,.NET 仍然會(huì)回退到非 SIMD 的實(shí)現(xiàn)方式,確保代碼的兼容性。
VectorXXX 為我們提供了一組靜態(tài)方法,用于創(chuàng)建和操作向量。例如,Vector128.Add 方法用于對(duì)兩個(gè) 128 位向量執(zhí)行加法運(yùn)算。
我們也可以直接使用運(yùn)算符號(hào)來進(jìn)行向量運(yùn)算,例如 +、-、*、/ 等。VectorXXX<T> 結(jié)構(gòu)體重載了這些運(yùn)算符,使得向量運(yùn)算更加直觀和簡(jiǎn)潔。
下面這個(gè)例子使用 Vector128<float> 來進(jìn)行浮點(diǎn)數(shù)的 SIMD 運(yùn)算:
using System.Runtime.Intrinsics;
// 創(chuàng)建兩個(gè) 128 位的浮點(diǎn)向量
Vector128<float> vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
Vector128<float> vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);
// 執(zhí)行加法運(yùn)算
// 等效于 vectorA + vectorB
var result = Vector128.Add(vectorA, vectorB);
// 輸出結(jié)果
Console.WriteLine($"Result: {result}");Result: <6, 8, 10, 12>
SIMD 指令集的使用
在使用 SIMD 指令集之前,通常需要檢查當(dāng)前平臺(tái)是否支持特定的指令集??梢酝ㄟ^調(diào)用指令集類的 IsSupported 屬性來進(jìn)行檢查。例如:
using System.Runtime.Intrinsics.X86; Console.WriteLine(Sse.IsSupported ? "SSE 指令集受支持" : "SSE 指令集不受支持");
一旦確認(rèn)指令集受支持,就可以使用該指令集類提供的靜態(tài)方法來執(zhí)行 SIMD 操作。例如,使用 Sse 類的 Add 方法來對(duì)兩個(gè) 128 位向量執(zhí)行加法運(yùn)算:
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
if (Sse.IsSupported)
{
// 創(chuàng)建兩個(gè) 128 位的浮點(diǎn)向量
Vector128<float> vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
Vector128<float> vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);
// 使用 SSE 指令集執(zhí)行加法運(yùn)算
var result = Sse.Add(vectorA, vectorB);
// 輸出結(jié)果
Console.WriteLine($"Result: {result}");
}
else
{
Console.WriteLine("SSE 指令集不受支持");
}Result: <6, 8, 10, 12>
如果是在 ARM 架構(gòu)上,可以使用 AdvSimd 類來執(zhí)行類似的操作:
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
if (AdvSimd.IsSupported)
{
// 創(chuàng)建兩個(gè) 128 位的浮點(diǎn)向量
Vector128<float> vectorA = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
Vector128<float> vectorB = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);
// 使用 AdvSimd 指令集執(zhí)行加法運(yùn)算
var result = AdvSimd.Add(vectorA, vectorB);
// 輸出結(jié)果
Console.WriteLine($"Result: {result}");
}
else
{
Console.WriteLine("AdvSimd 指令集不受支持");
}Result: <6, 8, 10, 12>
System.Numerics 命名空間中的 SIMD 支持
基于 System.Runtime.Intrinsics,.NET 還提供了別的更高級(jí)別的 SIMD 支持。
比如在 System.Numerics 這個(gè)命名空間提供了一些易于使用的類型,如 Vector<T> 和 Matrix4x4,簡(jiǎn)化了 SIMD 編程。
Vector<T> 結(jié)構(gòu)體
Vector<T> 是一個(gè)通用的向量類型,支持多種數(shù)值類型(如 int、float、double 等)。它會(huì)根據(jù)硬件能力自動(dòng)選擇最佳的向量大?。ㄈ?128 位或 256 位),從而實(shí)現(xiàn)跨平臺(tái)的 SIMD 優(yōu)化。
下面是一個(gè)使用 Vector<T> 進(jìn)行數(shù)組加法的示例:
var vectorSize = Vector<float>.Count; // 獲取向量大?。ㄔ貍€(gè)數(shù))
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<float>(arrayA, i);
var vb = new Vector<float>(arrayB, i);
(va + vb).CopyTo(resultArray, i);
}Vector2、Vector3 和 Vector4 結(jié)構(gòu)體
System.Numerics 命名空間還提供了 Vector2、Vector3 和 Vector4 結(jié)構(gòu)體,分別表示二維、三維和四維向量,常用于圖形和物理計(jì)算中。
using System.Numerics;
// 創(chuàng)建 Vector3 實(shí)例
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>
// 向量點(diǎn)乘
float dotProduct = Vector3.Dot(vector1, vector2);
Console.WriteLine($"Dot Product: {dotProduct}"); // 輸出: 32
// 向量歸一化
Vector3 normalized = Vector3.Normalize(vector1);
Console.WriteLine($"Normalized: {normalized}"); // 輸出: <0.2672612, 0.5345225, 0.8017837>如果我們?nèi)タ?Vector3 結(jié)構(gòu)體的源碼實(shí)現(xiàn),會(huì)發(fā)現(xiàn)它內(nèi)部使用了 SIMD 技術(shù)來優(yōu)化向量運(yùn)算:
public struct Vector3 : IEquatable<Vector3>, IFormattable
{
/// <summary>The X component of the vector.</summary>
public float X;
/// <summary>The Y component of the vector.</summary>
public float Y;
/// <summary>The Z component of the vector.</summary>
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 結(jié)構(gòu)體
System.Numerics 還提供了 Matrix2x2、Matrix3x2 和 Matrix4x4 結(jié)構(gòu)體,用于表示二維和三維空間中的矩陣,常用于變換和投影計(jì)算。
using System.Numerics;
// 創(chuàng)建一個(gè) 4x4 矩陣
Matrix4x4 matrix = Matrix4x4.CreateRotationX((float)(Math.PI / 4));
Vector3 point = new Vector3(1.0f, 0.0f, 0.0f);
// 使用矩陣變換點(diǎn)
Vector3 transformedPoint = Vector3.Transform(point, matrix);
Console.WriteLine($"Transformed Point: {transformedPoint}");Transformed Point: <1, 0, 0>
其他 SIMD 的使用場(chǎng)景舉例
字母大小寫轉(zhuǎn)換
在 ASCII 碼表的設(shè)計(jì)中,大寫字母和小寫字母之間的差異僅在于第 6 位(從右往左數(shù))。大寫字母的第 6 位為 0,而小寫字母的第 6 位為 1。因此,我們可以通過對(duì)字符的二進(jìn)制表示進(jìn)行按位操作來實(shí)現(xiàn)大小寫轉(zhuǎn)換。
示例:字母 A B C D 的 ASCII 編碼對(duì)照
| 字符 | ASCII 十進(jìn)制 | ASCII 十六進(jìn)制 | 二進(jìn)制表示 |
|---|---|---|---|
| A | 65 | 0x41 | 01000001 |
| B | 66 | 0x42 | 01000010 |
| C | 67 | 0x43 | 01000011 |
| D | 68 | 0x44 | 01000100 |
| a | 97 | 0x61 | 01100001 |
| b | 98 | 0x62 | 01100010 |
| c | 99 | 0x63 | 01100011 |
| d | 100 | 0x64 | 01100100 |
可以看到,對(duì)應(yīng)的大寫和小寫之間,二進(jìn)制的第 6 位(從右數(shù),值為 32,即 0x20)狀態(tài)不同:
- 大寫:第 6 位為 0
- 小寫:第 6 位為 1
這樣,只需用使用異或操作(XOR)即可實(shí)現(xiàn)大小寫轉(zhuǎn)換:
Console.WriteLine((char)('a' ^ 0x20)); // 輸出 A
Console.WriteLine((char)('A' ^ 0x20)); // 輸出 a
在 System.Text 命名空間中,.NET 提供了 Ascii 類,里面包含了一些用于 ASCII 字符處理的靜態(tài)方法。我們可以利用 SIMD 技術(shù)來實(shí)現(xiàn)高效的大小寫轉(zhuǎn)換。
public static class Ascii
{
public static OperationStatus ToUpper(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
public static OperationStatus ToLower(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
public static OperationStatus ToUpperInPlace(Span<byte> value, out int bytesWritten);
public static OperationStatus ToLowerInPlace(Span<byte> value, out int bytesWritten);
public static OperationStatus ToUpper(ReadOnlySpan<char> source, Span<char> destination, out int charsWritten);
public static OperationStatus ToLower(ReadOnlySpan<char> source, Span<char> destination, out int charsWritten);
public static OperationStatus ToUpperInPlace(Span<char> value, out int charsWritten);
public static OperationStatus ToLowerInPlace(Span<char> value, out int charsWritten);
}
public enum OperationStatus
{
/// <summary>The entire input buffer has been processed and the operation is complete.</summary>
Done,
/// <summary>The input is partially processed, up to what could fit into the destination buffer. The caller can enlarge the destination buffer, slice the buffers appropriately, and retry.</summary>
DestinationTooSmall,
/// <summary>The input is partially processed, up to the last valid chunk of the input that could be consumed. The caller can stitch the remaining unprocessed input with more data, slice the buffers appropriately, and retry.</summary>
NeedMoreData,
/// <summary>The input contained invalid bytes which could not be processed. If the input is partially processed, the destination contains the partial result. This guarantees that no additional data appended to the input will make the invalid sequence valid.</summary>
InvalidData,
}參數(shù)的重載有 byte 和 char 兩種,分別用于處理保存為 byte 和 char 類型的 ASCII 字符數(shù)據(jù)。
如果是從 IO 流中讀取數(shù)據(jù)進(jìn)行大小寫轉(zhuǎn)換,可以使用 Span<byte> 版本;如果是處理 char 數(shù)組或字符串,則使用 Span<char> 版本。
ToUpper 和 ToLower 方法會(huì)將源數(shù)據(jù)轉(zhuǎn)換為目標(biāo)數(shù)據(jù),并返回一個(gè) OperationStatus 枚舉值,指示操作的狀態(tài)。
ToUpperInPlace 和 ToLowerInPlace 方法則會(huì)直接在原始數(shù)據(jù)上進(jìn)行大小寫轉(zhuǎn)換。
需要注意的是,這些方法僅處理 ASCII 范圍內(nèi)的字符(0-127),對(duì)于非 ASCII 字符不會(huì)進(jìn)行任何轉(zhuǎn)換。
這些方法并不能替代 string.ToUpper 等方法來獲取 string 的大寫或小寫形式。需考慮 string 和 Span<byte>, Span<char> 之間的轉(zhuǎn)換開銷,最終性能并不一定優(yōu)于直接使用 string.ToUpper 等方法。
可以參考微軟的開源項(xiàng)目 Garnet 中的使用場(chǎng)景 AsciiUtils.cs
Ascii.ToUpperInPlace(Span<char> value, out int charsWritten) 的核心實(shí)現(xiàn)經(jīng)整理后大致如下:
void ToUpperInPlace(Span<char> value)
{
// 將 Span<char> 轉(zhuǎn)換成 Span<ushort>(char 占 2 字節(jié),方便 SIMD 處理)
var buffer = MemoryMarshal.Cast<char, ushort>(value);
// 獲取元素?cái)?shù)量(ushort 數(shù)量)
var elementCount = (uint)buffer.Length;
// 每個(gè)向量能處理多少個(gè) ushort
var numElementsPerVector = (uint)(Unsafe.SizeOf<Vector128<byte>>() / sizeof(ushort));
// 如果支持 SIMD 且數(shù)據(jù)足夠,否則走普通循環(huán)
if (Vector128.IsHardwareAccelerated && elementCount >= numElementsPerVector)
{
// 有符號(hào)最小值 (0x8000) 用于偏移正確比較
ushort sourceSignedMinValue = (ushort)(1 << (8 * sizeof(ushort) - 1));
// 'a' 的基準(zhǔn)向量(所有元素都是 0x8000 + 'a')
var subtractionVector = Vector128.Create((ushort)(sourceSignedMinValue + 'a'));
// 26 個(gè)字母范圍偏移向量(所有元素都是 0x8000 + 26)
var comparisonVector = Vector128.Create((ushort)(sourceSignedMinValue + 26));
// 大小寫差值向量(0x20,所有元素都是這個(gè)值)
var caseConversionVector = Vector128.Create((ushort)0x20);
// 向量化循環(huán)索引
uint i = 0;
// 可以整除的元素個(gè)數(shù)
uint n = elementCount - (elementCount % numElementsPerVector);
// 向量化批量處理
for (; i < n; i += numElementsPerVector)
{
// 加載當(dāng)前批次的向量數(shù)據(jù)
var srcVector = Vector128.LoadUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i));
// 計(jì)算 src - 'a' 基準(zhǔn),并判斷是否小于 26(即在 a..z 范圍)
var matches = SignedLessThan(srcVector - subtractionVector, comparisonVector);
// 對(duì)匹配的小寫字母執(zhí)行大小寫轉(zhuǎn)換(異或 0x20 得到大寫)
srcVector ^= matches & caseConversionVector;
// 存回修改后的向量數(shù)據(jù)
srcVector.StoreUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i));
}
// 處理剩余不足一個(gè)向量大小的元素
for (; i < elementCount; i++)
{
// 讀取當(dāng)前字符
ushort c = Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i);
// 如果是小寫字母則轉(zhuǎn)為大寫
if (c is >= 'a' and <= 'z')
{
c = (ushort)(c - 0x20);
}
// 寫回結(jié)果
Unsafe.Add(ref MemoryMarshal.GetReference(buffer), (int)i) = c;
}
}
else
{
// 非向量化處理,每個(gè)元素單獨(dú)判斷
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;
}
}
}
// 有符號(hào)比較(用于判斷 a..z 范圍)
Vector128<ushort> SignedLessThan(Vector128<ushort> left, Vector128<ushort> right)
{
// 將 ushort 當(dāng)成 short 做有符號(hào)比較,然后再轉(zhuǎn)換回 ushort 掩碼
return Vector128.LessThan(left.AsInt16(), right.AsInt16()).AsInt16().AsUInt16();
}調(diào)用上述方法可以高效地將 ASCII 字符串轉(zhuǎn)換為大寫形式:
string input = "Hello World! This is a Test String."; Span<char> span = input.ToCharArray(); ToUpperInPlace(span); string result = new string(span); Console.WriteLine(result); // 輸出: "HELLO WORLD! THIS IS A TEST STRING."
實(shí)際的 Ascii 類實(shí)現(xiàn)要復(fù)雜得多,包含了更多的邊界檢查和錯(cuò)誤處理邏輯,上述代碼僅僅是為了說明核心的 SIMD 思路的簡(jiǎn)化版本。
使用 Ascii.UpperInPlace 的示例:
using System.Text; string input = "Hello World! This is a Test String. 這部分不會(huì)被轉(zhuǎn)換。"; Span<char> span = input.ToCharArray(); Ascii.ToUpperInPlace(span, out int charsWritten); // charsWritten 表示實(shí)際轉(zhuǎn)換的字符數(shù),非 ASCII 字符不會(huì)被轉(zhuǎn)換 string result = new string(span[..charsWritten]); Console.WriteLine(result); // 輸出: "HELLO WORLD! THIS IS A TEST STRING."
二進(jìn)制/位操作
SIMD 技術(shù)非常適合處理大量的二進(jìn)制數(shù)據(jù)或位操作。例如,BinaryPrimitives.ReverseEndianness 方法利用 SIMD 來高效地反轉(zhuǎn)字節(jié)序:
Span<ushort> data = [0x1234, 0xABCD, 0x5678, 0xEF01];
Span<ushort> reversedData = stackalloc ushort[4];
BinaryPrimitives.ReverseEndianness(data, reversedData);
foreach (var value in reversedData)
{
Console.WriteLine(value.ToString("X4"));
}3412 CDAB 7856 01EF
總結(jié)
SIMD 技術(shù)在 .NET 中提供了強(qiáng)大的并行計(jì)算能力,能夠顯著提升處理大量數(shù)據(jù)時(shí)的性能。通過 System.Runtime.Intrinsics 和 System.Numerics,我們可以方便地利用 SIMD 指令集進(jìn)行高效的向量化運(yùn)算。
很多基礎(chǔ)庫(kù)已經(jīng)內(nèi)置了 SIMD 優(yōu)化,開發(fā)者在日常編程中可以通過這些庫(kù)間接受益于 SIMD 技術(shù),而無需深入了解底層實(shí)現(xiàn)細(xì)節(jié)。當(dāng)然,對(duì)于性能敏感的應(yīng)用場(chǎng)景,理解和直接使用 SIMD 指令集仍然是非常有價(jià)值的。
到此這篇關(guān)于在 .NET 中使用 SIMD的操作方法的文章就介紹到這了,更多相關(guān).net使用simd內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ASP.NET MVC傳送參數(shù)至服務(wù)端詳解及實(shí)例
這篇文章主要介紹了ASP.NET MVC傳送參數(shù)至服務(wù)端詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2016-11-11
.NET?Core?實(shí)現(xiàn)一個(gè)自定義日志記錄器
在應(yīng)用程序中,日志記錄是一個(gè)至關(guān)重要的功能,不僅有助于調(diào)試和監(jiān)控應(yīng)用程序,還能幫助我們了解應(yīng)用程序的運(yùn)行狀態(tài),所以本文就將使用.NET?Core?實(shí)現(xiàn)一個(gè)自定義日志記錄器,需要的可以參考下2024-12-12
asp.net中virtual和abstract的區(qū)別分析
這篇文章主要介紹了asp.net中virtual和abstract的區(qū)別,較為詳細(xì)的分析了virtual與abstract的概念與具體用法,并以實(shí)例的形式予以總結(jié)歸納,需要的朋友可以參考下2014-10-10
uni-app結(jié)合.NET?7實(shí)現(xiàn)微信小程序訂閱消息推送
本文主要介紹了uni-app結(jié)合.NET?7實(shí)現(xiàn)微信小程序訂閱消息推送,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
ASP.NET中使用GridView實(shí)現(xiàn)分級(jí)顯示的代碼
在實(shí)際項(xiàng)目開發(fā)中,往往需要用到在頁面上對(duì)列表的項(xiàng)目實(shí)現(xiàn)分級(jí)顯示,在 ASP.NET中沒有現(xiàn)成的控件。2010-06-06
ASP.NET中實(shí)現(xiàn)文件的保護(hù)性下載基礎(chǔ)篇
許多時(shí)候,我們需要在因特網(wǎng)上提供文件下載服務(wù),但是又要防止未經(jīng)授權(quán)的下載,這時(shí)該怎么辦?本文將為讀者詳細(xì)介紹一種使用ASP.NET實(shí)現(xiàn)的HTTP處理程序的解決方案。2011-02-02

