C#泛型类用IBinaryArithmetic做算术为何产生代码膨胀?
.NET 7泛型类算术运算性能下降的原因与解决办法
在.NET 7中使用泛型类(约束为IBinaryInteger<T>)实现跨类型算术运算时,会出现性能明显低于非泛型类的情况。从反汇编代码能看到,泛型类里的*和+运算符被编译成了接口方法调用,而非像非泛型类那样直接生成imul、add这类原生CPU指令。
原因分析
- 非泛型类的直接优化:对于
uint、ulong这类具体值类型,编译器在编译期就能确定运算符对应的原生指令,直接生成高效的imul和add操作,没有额外调用开销。 - 泛型接口的方法调用开销:
IBinaryInteger<T>接口的运算符是通过静态方法(如Multiply、Add)实现的,泛型类编译时无法确定T的具体类型,只能生成通用的接口方法调用代码。JIT编译器在.NET 7中对这类泛型数值接口的内联优化支持不足,无法将接口调用替换为原生指令,导致每次运算都要走方法调用流程,增加了性能开销。 - .NET 7泛型数值优化局限:.NET 7引入了
IBinaryInteger<T>等泛型数值接口,但JIT编译器对这类接口的特化优化还不完善,无法像处理具体值类型那样直接生成最优机器码。
解决办法
1. 强制JIT内联优化
给泛型类的Step方法添加[MethodImpl(MethodImplOptions.AggressiveInlining)]特性,提示JIT编译器尽可能内联接口方法调用,部分场景下JIT会将接口调用替换为原生指令:
using System.Runtime.CompilerServices; public class UClass<T> where T : IBinaryInteger<T> { // ... 其他代码 ... [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Step() { State = State * Multiplier + Increment; } }
2. 类型特化实现
针对需要的数值类型编写特化的泛型实现,让编译器能生成对应类型的原生指令:
public static class ArithmeticHelper { public static void Step<T>(ref T state, T multiplier, T increment) where T : IBinaryInteger<T> { if (typeof(T) == typeof(uint)) { ref uint uintState = ref Unsafe.As<T, uint>(ref state); uint uintMultiplier = Unsafe.As<T, uint>(ref multiplier); uint uintIncrement = Unsafe.As<T, uint>(ref increment); uintState = uintState * uintMultiplier + uintIncrement; return; } if (typeof(T) == typeof(ulong)) { ref ulong ulongState = ref Unsafe.As<T, ulong>(ref state); ulong ulongMultiplier = Unsafe.As<T, ulong>(ref multiplier); ulong ulongIncrement = Unsafe.As<T, ulong>(ref increment); ulongState = ulongState * ulongMultiplier + ulongIncrement; return; } // 其他类型通用实现 state = state * multiplier + increment; } } // 泛型类中调用特化方法 public class UClass<T> where T : IBinaryInteger<T> { // ... 其他代码 ... public void Step() { ArithmeticHelper.Step(ref State, Multiplier, Increment); } }
3. 升级到.NET 8及以上版本
.NET 8对泛型数值接口的JIT优化做了大幅改进,能自动将IBinaryInteger<T>的运算符调用优化为原生指令,无需额外修改代码就能获得和非泛型类相近的性能。
4. 手动Unsafe操作(不推荐)
在极端性能需求下,可通过Unsafe类直接将T转换为原生数值类型进行运算,但会失去类型安全性,仅适合确定类型范围的场景。
原始代码与反汇编
原始C#源码
using System.Numerics; namespace ConsoleApp27 { internal class Program { static void Main(string[] args) { var UIntInstance = new UIntClass(1, 1, 1); var ULongInstance = new ULongClass(1, 1, 1); var UInstance = new UClass<ulong>(1, 1, 1); UIntInstance.Step(); ULongInstance.Step(); UInstance.Step(); } } public class UIntClass { public uint State; public uint Multiplier; public uint Increment; public UIntClass(uint state, uint multiplier, uint increment) { State = state; Multiplier = multiplier; Increment = increment; } public void Step() { State = State * Multiplier + Increment; } } public class ULongClass { public ulong State; public ulong Multiplier; public ulong Increment; public ULongClass(ulong state, ulong multiplier, ulong increment) { State = state; Multiplier = multiplier; Increment = increment; } public void Step() { State = State * Multiplier + Increment; } } public class UClass<T> where T : IBinaryInteger<T> { public T State; public T Multiplier; public T Increment; public UClass(T state, T multiplier, T increment) { State = state; Multiplier = multiplier; Increment = increment; } public void Step() { State = State * Multiplier + Increment; } } }
非泛型类核心反汇编
UIntClass.Step()
00007FFADD1A795D mov rax,qword ptr [rbp+10h] 00007FFADD1A7961 mov eax,dword ptr [rax+8] 00007FFADD1A7964 mov rdx,qword ptr [rbp+10h] 00007FFADD1A7968 imul eax,dword ptr [rdx+0Ch] 00007FFADD1A796C mov rdx,qword ptr [rbp+10h] 00007FFADD1A7970 add eax,dword ptr [rdx+10h] 00007FFADD1A7973 mov rdx,qword ptr [rbp+10h] 00007FFADD1A7977 mov dword ptr [rdx+8],eax
ULongClass.Step()
00007FFADD19902D mov rax,qword ptr [rbp+10h] 00007FFADD199031 mov rax,qword ptr [rax+8] 00007FFADD199035 mov rdx,qword ptr [rbp+10h] 00007FFADD199039 imul rax,qword ptr [rdx+10h] 00007FFADD19903E mov rdx,qword ptr [rbp+10h] 00007FFADD199042 add rax,qword ptr [rdx+18h] 00007FFADD199046 mov rdx,qword ptr [rbp+10h] 00007FFADD19904A mov qword ptr [rdx+8],rax
泛型类UClass.Step()核心反汇编
00007FFADD1C9093 mov rcx,qword ptr [rbp+10h] 00007FFADD1C9097 mov qword ptr [rbp-10h],rcx 00007FFADD1C909B mov rcx,qword ptr [rbp+10h] 00007FFADD1C909F mov rcx,qword ptr [rcx+8] 00007FFADD1C90A3 mov rdx,qword ptr [rbp+10h] 00007FFADD1C90A7 mov rdx,qword ptr [rdx+10h] 00007FFADD1C90AB call qword ptr [CLRStub[MethodDescPrestub]@00007FFADD183978 (07FFADD183978h)] 00007FFADD1C90B1 mov qword ptr [rbp-18h],rax 00007FFADD1C90B5 mov rdx,qword ptr [rbp+10h] 00007FFADD1C90B9 mov rdx,qword ptr [rdx+18h] 00007FFADD1C90BD mov rcx,qword ptr [rbp-18h] 00007FFADD1C90C1 call qword ptr [CLRStub[MethodDescPrestub]@00007FFADD1835A0 (07FFADD1835A0h)] 00007FFADD1C90C7 mov qword ptr [rbp-20h],rax 00007FFADD1C90CB mov rax,qword ptr [rbp-10h] 00007FFADD1C90CF mov rdx,qword ptr [rbp-20h] 00007FFADD1C90D3 mov qword ptr [rax+8],rdx
内容的提问来源于stack exchange,提问作者BoCoKeith
相关产品推荐
相关产品推荐

