当前位置:  开发笔记 > 前端 > 正文

为什么Math.DivRem效率低下?

如何解决《为什么Math.DivRem效率低下?》经验,为你挑选了3个好方法。

在我的计算机中,此代码需要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);
}

CIL

.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)!



1> Joshua..:

哎呀.这个函数存在的唯一原因是为了利用CPU指令,他们甚至没有这样做!


但是,在相关的说明中,我认为很遗憾的是,如此少的语言为产品大小大于操作数的乘法提供任何支持,或者其被除数大于除数的除数.可以将乘法器的操作数放大到结果大小,或者按比例缩放除数的大小以匹配被除数,但是许多处理器都有混合大小操作数的指令,并且不使用它们是一种耻辱.
不.如果他们这样做,.NET Reflector会告诉你这个函数在本机存根中.
实际上,即使没有CPU函数产生余数和商,该函数也可能有用,如果它通过计算商来开始,乘以除数,并从被除数中减去.乘法在几乎每个平台上都比分割快,而在某些平台上,速度有一个完整的数量级差异,因此使用除法,乘法和减法可能几乎是使用两个除法的两倍.
@supercat当你在乘法之前将操作数从32位转换为64位时,至少某些版本的.net抖动可以优化32位*32位=> 64位.无需语言支持.另一方面,保持64位乘法结果所需的128位类型严重缺失.
@CodesInChaos:即使窥孔优化器可以用硬件内在函数替换某些构造,除非语言标准化强烈鼓励代码生成器进行窥孔优化的某些形式,否则很可能最终会出现编写一段代码的最快方式在两个"兼容"编译器中的每一个上,最终在另一个上慢得多.我不喜欢程序员应该请求他们不想要的操作以获得他们想要的操作的想法,希望优化器将省略程序员从未想过的操作.

2> Die in Sente..:

哇,真的看起来很蠢,不是吗?

问题在于 - 根据Lidin的微软出版社出版的".NET IL Assembler" - IL rem和div算术指令就是这样:计算余数和计算除数.

除了否定操作之外的所有算术运算都从堆栈中获取两个操作数并将结果放在堆栈上.

显然,IL汇编语言的设计方式,不可能有一个产生两个输出的IL指令并将它们推送到eval堆栈上.鉴于此限制,您不能在IL汇编程序中使用除法指令来计算x86 DIV或IDIV指令的方式.

IL旨在实现安全性,可验证性和稳定性,而非性能.任何拥有计算密集型应用程序且主要关注性能的人都将使用本机代码而不是.NET.

我最近参加了Supercomputing '08,在其中一个技术会议上,Microsoft Compute Server的传播者给出了粗略的经验法则,即.NET通常是本机代码速度的一半 - 这正是这里的情况!


虽然这是真的,但是没有理由不能在运行时实现`Math.DivRem`并用`[MethodImpl(MethodImplOptions.InternalCall),SecuritySafeCritical]`标记``System.Math上的其他方法的_many_是.
您是否拥有IL本身无法在堆栈上生成两个值的声明来源?我不是说这是假的; 很容易想象JITter大量使用这个假设,但是一个合适的来源会很方便.

3> Bob..:

虽然.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中的一般情况.

推荐阅读
Gbom2402851125
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有