콘텐츠로 이동

C# SIMD — System.Numerics & Vector 활용

SIMD(Single Instruction Multiple Data)는 하나의 명령어로 여러 데이터를 동시에 처리합니다. C#은 System.Numerics 네임스페이스를 통해 JIT가 SIMD 명령어를 자동 생성하도록 지원합니다.


1. Vector — 하드웨어 크기 자동 적응

섹션 제목: “1. Vector — 하드웨어 크기 자동 적응”
using System.Numerics;
// 현재 하드웨어의 SIMD 레지스터 크기에 맞춰 자동 조정
// SSE2: 128비트 → int 4개 / AVX2: 256비트 → int 8개
Console.WriteLine($"Vector<int> 크기: {Vector<int>.Count}"); // 4 또는 8
float[] SumArrays(float[] a, float[] b)
{
var result = new float[a.Length];
int vectorSize = Vector<float>.Count;
int i = 0;
// SIMD 처리
for (; i <= a.Length - vectorSize; i += vectorSize)
{
var va = new Vector<float>(a, i);
var vb = new Vector<float>(b, i);
(va + vb).CopyTo(result, i);
}
// 나머지 스칼라 처리
for (; i < a.Length; i++)
result[i] = a[i] + b[i];
return result;
}

2. Vector128 / Vector256 — 고정 크기 벡터

섹션 제목: “2. Vector128 / Vector256 — 고정 크기 벡터”
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
// AVX2가 지원되면 256비트 처리
void AbsoluteValue(float[] data)
{
if (Avx.IsSupported)
{
var signMask = Vector256.Create(0x7FFFFFFF).AsSingle();
int i = 0;
for (; i <= data.Length - 8; i += 8)
{
var v = Vector256.LoadUnsafe(ref data[i]);
var abs = Avx.And(v, signMask);
abs.StoreUnsafe(ref data[i]);
}
for (; i < data.Length; i++)
data[i] = MathF.Abs(data[i]);
}
else
{
for (int i = 0; i < data.Length; i++)
data[i] = MathF.Abs(data[i]);
}
}

using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
float DotProduct(float[] a, float[] b)
{
if (!Avx.IsSupported || a.Length != b.Length)
return ScalarDot(a, b);
var sum = Vector256<float>.Zero;
int i = 0;
for (; i <= a.Length - 8; i += 8)
{
var va = Vector256.LoadUnsafe(ref a[i]);
var vb = Vector256.LoadUnsafe(ref b[i]);
sum = Fma.IsSupported
? Fma.MultiplyAdd(va, vb, sum) // FMA: a*b + sum (한 명령)
: Avx.Add(Avx.Multiply(va, vb), sum);
}
// 256비트 → 스칼라 합산
var sum128 = Sse.Add(sum.GetLower(), sum.GetUpper());
sum128 = Sse.Add(sum128, Sse.MoveHighToLow(sum128, sum128));
sum128 = Sse.AddScalar(sum128, Sse.Shuffle(sum128, sum128, 1));
float result = sum128.ToScalar();
for (; i < a.Length; i++)
result += a[i] * b[i];
return result;
}

using System.Runtime.Intrinsics.Arm;
void MultiplyAdd(float[] dst, float[] src, float scalar)
{
if (AdvSimd.IsSupported)
{
var vs = Vector64.Create(scalar);
int i = 0;
for (; i <= dst.Length - 4; i += 4)
{
var vd = AdvSimd.LoadVector128(ref dst[i]);
var vsrc = AdvSimd.LoadVector128(ref src[i]);
var result = AdvSimd.FusedMultiplyAdd(vd, vsrc,
Vector128.Create(scalar));
AdvSimd.Store(ref dst[i], result);
}
for (; i < dst.Length; i++)
dst[i] += src[i] * scalar;
}
}

using System.Numerics;
int CountChar(ReadOnlySpan<byte> data, byte target)
{
int count = 0;
var vTarget = new Vector<byte>(target);
int vectorSize = Vector<byte>.Count;
int i = 0;
for (; i <= data.Length - vectorSize; i += vectorSize)
{
var chunk = new Vector<byte>(data[i..]);
var eq = Vector.Equals(chunk, vTarget);
// 일치 바이트 수 카운트
count += -Vector.Dot(eq, Vector<byte>.One);
}
for (; i < data.Length; i++)
if (data[i] == target) count++;
return count;
}

항목권장 사항
하드웨어 감지Avx2.IsSupported, AdvSimd.IsSupported 확인 후 분기
정렬가능하면 16/32바이트 정렬된 메모리 사용
루프 잔여SIMD 루프 후 스칼라 처리 코드 필수
벤치마크BenchmarkDotNet으로 실제 속도 측정
크로스 플랫폼Vector<T> 우선, Intrinsics는 폴백 포함

| Method | Mean | Speedup |
|---------------- |----------:|--------:|
| ScalarDotPro | 2,140.0 ns | 1.0x |
| Vector_DotProd | 312.5 ns | 6.8x |
| Avx2_DotProduct | 178.2 ns | 12.0x |

고수준 수치 연산 API로 SIMD를 자동 적용합니다.

using System.Numerics.Tensors;
float[] a = [1f, 2f, 3f, 4f, 5f];
float[] b = [10f, 20f, 30f, 40f, 50f];
float[] result = new float[5];
// 벡터 덧셈 (내부적으로 SIMD 사용)
TensorPrimitives.Add(a, b, result);
// 코사인 유사도 (AI/ML 임베딩 비교에 활용)
float similarity = TensorPrimitives.CosineSimilarity(a, b);
// 소프트맥스
float[] probs = new float[5];
TensorPrimitives.SoftMax(a, probs);
// L2 노름
float norm = TensorPrimitives.Norm(a);
// 요소별 곱셈
TensorPrimitives.Multiply(a, b, result);
// 내적 (Dot Product)
float dot = TensorPrimitives.Dot<float>(a, b); // 550

9. Vector128/Vector256 조건부 활성화

섹션 제목: “9. Vector128/Vector256 조건부 활성화”
using System.Runtime.Intrinsics;
// .NET 7+ 크로스 플랫폼 고정 크기 벡터 API
void NormalizeVectors(Span<float> data)
{
int i = 0;
// Vector256 (AVX2: x86, SVE: ARM)
if (Vector256.IsHardwareAccelerated)
{
for (; i <= data.Length - 8; i += 8)
{
var v = Vector256.LoadUnsafe(ref data[i]);
var norm = Vector256.Sqrt(Vector256.Dot(v, v));
(v / norm).StoreUnsafe(ref data[i]);
}
}
// Vector128 폴백 (SSE2 / NEON)
else if (Vector128.IsHardwareAccelerated)
{
for (; i <= data.Length - 4; i += 4)
{
var v = Vector128.LoadUnsafe(ref data[i]);
var norm = Vector128.Sqrt(Vector128.Dot(v, v));
(v / norm).StoreUnsafe(ref data[i]);
}
}
// 스칼라 잔여 처리
for (; i < data.Length; i++)
data[i] /= MathF.Sqrt(data[i] * data[i]);
}

C# SIMD는 Vector<T>로 크로스 플랫폼 벡터화를 시작하고, 성능이 더 필요하면 Vector256과 플랫폼별 Intrinsics로 내려가는 계층적 접근이 효과적입니다. .NET 8+에서는 TensorPrimitives가 AI/ML 워크로드에 필요한 고수준 수치 연산을 SIMD로 자동 가속합니다. 이미지 처리, 오디오 DSP, 물리 시뮬레이션, 머신러닝 전처리 등 대용량 수치 연산에서 6~12배의 속도 향상을 기대할 수 있습니다.