在我的计算机中,此代码需要17秒(1000万次):
static void Main(string[] args) { var sw = new Stopwatch(); sw.Start(); int r; for (int i = 1; i <= 100000000; i++) { for (int j = 1; j <= 10; j++) { MyDivRem (i,j, out r); } } Console.WriteLine(sw.ElapsedMilliseconds); } static int MyDivRem(int dividend, int divisor, out int remainder) { int quotient = dividend / divisor; remainder = dividend - divisor * quotient; return quotient; }
而Math.DivRem需要27秒.
.NET Reflector为我提供了Math.DivRem的代码:
public static int DivRem(int a, int b, out int result) { result = a % b; return (a / b); }
.method public hidebysig static int32 DivRem(int32 a, int32 b, [out] int32& result) cil managed { .maxstack 8 L_0000: ldarg.2 L_0001: ldarg.0 L_0002: ldarg.1 L_0003: rem L_0004: stind.i4 L_0005: ldarg.0 L_0006: ldarg.1 L_0007: div L_0008: ret }
从理论上讲,对于具有多个内核的计算机来说可能会更快,但实际上它不需要首先执行两个操作,因为当使用DIV或IDIV进行整数除法时,x86 CPU会返回商和余数(http: //www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_6/CH06-2.html#HEADING2-451)!
哎呀.这个函数存在的唯一原因是为了利用CPU指令,他们甚至没有这样做!
哇,真的看起来很蠢,不是吗?
问题在于 - 根据Lidin的微软出版社出版的".NET IL Assembler" - IL rem和div算术指令就是这样:计算余数和计算除数.
除了否定操作之外的所有算术运算都从堆栈中获取两个操作数并将结果放在堆栈上.
显然,IL汇编语言的设计方式,不可能有一个产生两个输出的IL指令并将它们推送到eval堆栈上.鉴于此限制,您不能在IL汇编程序中使用除法指令来计算x86 DIV或IDIV指令的方式.
IL旨在实现安全性,可验证性和稳定性,而非性能.任何拥有计算密集型应用程序且主要关注性能的人都将使用本机代码而不是.NET.
我最近参加了Supercomputing '08,在其中一个技术会议上,Microsoft Compute Server的传播者给出了粗略的经验法则,即.NET通常是本机代码速度的一半 - 这正是这里的情况!
虽然.NET Framework 4.6.2仍然使用次优的模数和除法,但.NET Core(CoreCLR)目前用减法替换除法:
public static int DivRem(int a, int b, out int result) { // TODO https://github.com/dotnet/coreclr/issues/3439: // Restore to using % and / when the JIT is able to eliminate one of the idivs. // In the meantime, a * and - is measurably faster than an extra /. int div = a / b; result = a - (div * b); return div; }
并且有一个未决问题要么专门改进DivRem
(通过内在),要么检测和优化 RyuJIT中的一般情况.