代码之家  ›  专栏  ›  技术社区  ›  Oleksii G.

为什么数学课效率这么低?

  •  32
  • Oleksii G.  · 技术社区  · 17 年前

    在我的计算机中,此代码需要17秒(10亿次):

    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;
    }
    

    .NET Reflector

    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
    }
    

    从理论上讲,多核计算机的速度可能更快,但事实上,它首先不需要执行两个操作,因为x86 CPU返回 当他们使用DIV或IDIV进行整数除法时( http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_6/CH06-2.html#HEADING2-451 )!

    11 回复  |  直到 8 年前
        1
  •  19
  •   Joshua    17 年前

        2
  •  14
  •   Die in Sente    17 年前

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

    除求反运算外,所有算术运算都从堆栈中取出两个操作数,并将结果放在堆栈上。

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

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

        3
  •  13
  •   Bob    6 年前

    尽管.NET Framework 4.6.2仍然使用次优的模除运算,.NET核心(CoreCLR) currently 将除法替换为减法:

        public static int DivRem(int a, int b, out int result)
        {
            // TODO https://github.com/dotnet/runtime/issues/5213:
            // 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;
        }
    

    这两者都有一个悬而未决的问题 improve DivRem specifically detect and optimise the general case 在龙井。

        4
  •  2
  •   Sander    17 年前

    答案可能是没有人认为这是一个优先事项——这已经足够好了。事实上,任何新版本的.NET Framework都没有解决这一问题,这表明这种情况很少被使用——很可能从来没有人抱怨过。

        5
  •  2
  •   rmeador    17 年前

        6
  •  1
  •   Austin Salonen gmlacrosse    17 年前

    Math.DivRem = 11.029 sec, 11.780 sec
    MyDivRem = 27.330 sec, 27.562 sec
    DivRem = 29.689 sec, 30.338 sec
    

    FWIW,我正在运行Intel Core 2 Duo。

    在发布版本中:

    Math.DivRem = 10.314
    DivRem = 10.324
    MyDivRem = 5.380
    

        7
  •  1
  •   Chris Ammerman    17 年前

    效率很可能取决于所涉及的数量。您正在测试可用问题空间的一小部分,并且所有问题都是前置的。您正在检查前100万*10=10亿个连续输入组合,但实际问题空间约为42亿平方,或1.8e19个组合。

    像这样的一般库数学运算的性能需要在整个问题空间中分摊。我想看看更规范化的输入分布的结果。

        8
  •  0
  •   Jim Carnicelli    16 年前

    至于它存在的原因,我猜它的存在部分是为了完整性,部分是为了其他语言的好处,这些语言可能不具备易于使用的整数除法和模计算实现。

        9
  •  0
  •   codekaizen    15 年前

    下面是一些C#用法 Math.DivRem() :

        [Fact]
        public void MathTest()
        {
            for (var i = 1; i <= 10; i++)
            {
                int remainder;
                var result = Math.DivRem(10, i, out remainder);
                // Use the values so they aren't optimized away
                Assert.True(result >= 0);
                Assert.True(remainder >= 0);
            }
        }
    

    以下是相应的IL:

    .method public hidebysig instance void MathTest() cil managed
    {
        .custom instance void [xunit]Xunit.FactAttribute::.ctor()
        .maxstack 3
        .locals init (
            [0] int32 i,
            [1] int32 remainder,
            [2] int32 result)
        L_0000: ldc.i4.1 
        L_0001: stloc.0 
        L_0002: br.s L_002b
        L_0004: ldc.i4.s 10
        L_0006: ldloc.0 
        L_0007: ldloca.s remainder
        L_0009: call int32 [mscorlib]System.Math::DivRem(int32, int32, int32&)
        L_000e: stloc.2 
        L_000f: ldloc.2 
        L_0010: ldc.i4.0 
        L_0011: clt 
        L_0013: ldc.i4.0 
        L_0014: ceq 
        L_0016: call void [xunit]Xunit.Assert::True(bool)
        L_001b: ldloc.1 
        L_001c: ldc.i4.0 
        L_001d: clt 
        L_001f: ldc.i4.0 
        L_0020: ceq 
        L_0022: call void [xunit]Xunit.Assert::True(bool)
        L_0027: ldloc.0 
        L_0028: ldc.i4.1 
        L_0029: add 
        L_002a: stloc.0 
        L_002b: ldloc.0 
        L_002c: ldc.i4.s 10
        L_002e: ble.s L_0004
        L_0030: ret 
    }
    

           for (var i = 1; i <= 10; i++)
    00000000  push        ebp 
    00000001  mov         ebp,esp 
    00000003  push        esi 
    00000004  push        eax 
    00000005  xor         eax,eax 
    00000007  mov         dword ptr [ebp-8],eax 
    0000000a  mov         esi,1 
            {
                int remainder;
                var result = Math.DivRem(10, i, out remainder);
    0000000f  mov         eax,0Ah 
    00000014  cdq 
    00000015  idiv        eax,esi 
    00000017  mov         dword ptr [ebp-8],edx 
    0000001a  mov         eax,0Ah 
    0000001f  cdq 
    00000020  idiv        eax,esi 
    

    注意 2. 呼叫 idiv . 第一个存储其余的( EDX remainder 堆栈上的参数。第二个是确定商( EAX ).实际上不需要第二次通话,因为 在第一次调用后具有正确的值 伊迪夫 .

        10
  •  0
  •   Brad Wilson    8 年前

    15170 MyDivRem
    29579 DivRem (same code as below)
    29579 Math.DivRem
    30031 inlined
    

    试验略有改变;我在返回值中添加了赋值,并且正在运行发布版本。

    意见:

    您似乎找到了一个很好的优化;)

        11
  •  -3
  •   Peter Mortensen Pieter Jan Bonestroo    8 年前