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]); }}3. Vector256 도트 프로덕트
섹션 제목: “3. Vector256 도트 프로덕트”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;}4. ARM NEON 지원 (AdvSimd)
섹션 제목: “4. ARM NEON 지원 (AdvSimd)”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; }}5. Vector로 문자열 검색 가속
섹션 제목: “5. Vector로 문자열 검색 가속”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;}6. 성능 가이드라인
섹션 제목: “6. 성능 가이드라인”| 항목 | 권장 사항 |
|---|---|
| 하드웨어 감지 | Avx2.IsSupported, AdvSimd.IsSupported 확인 후 분기 |
| 정렬 | 가능하면 16/32바이트 정렬된 메모리 사용 |
| 루프 잔여 | SIMD 루프 후 스칼라 처리 코드 필수 |
| 벤치마크 | BenchmarkDotNet으로 실제 속도 측정 |
| 크로스 플랫폼 | Vector<T> 우선, Intrinsics는 폴백 포함 |
7. BenchmarkDotNet 측정 예시
섹션 제목: “7. BenchmarkDotNet 측정 예시”| Method | Mean | Speedup ||---------------- |----------:|--------:|| ScalarDotPro | 2,140.0 ns | 1.0x || Vector_DotProd | 312.5 ns | 6.8x || Avx2_DotProduct | 178.2 ns | 12.0x |C# SIMD는 Vector<T>로 크로스 플랫폼 벡터화를 시작하고, 성능이 더 필요하면 Vector256과 플랫폼별 Intrinsics로 내려가는 계층적 접근이 효과적입니다. 이미지 처리, 오디오 DSP, 물리 시뮬레이션, 머신러닝 전처리 등 대용량 수치 연산에서 6~12배의 속도 향상을 기대할 수 있습니다.