Why is Math.DivRem so inefficient?

后端 未结 11 1942
慢半拍i
慢半拍i 2020-12-08 18:59

In my computer this code takes 17 seconds (1000 millions times):

static void Main(string[] args) {
   var sw = new Stopwatch(); sw.Start();
   int r;
   for          


        
相关标签:
11条回答
  • 2020-12-08 19:50

    Here are my numbers:

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

    The test was slightly changed; I added assignment to the return value and was running release build.

    Core 2 Duo 2.4

    Opinion:

    You seemed to have found a nice optimization ;)

    0 讨论(0)
  • 2020-12-08 19:52

    The efficiency may very well depend on the numbers involved. You are testing a TINY fraction of the available problem space, and all front-loaded. You are checking the first 1 million * 10 = 1 billion contiguous input combinations, but the actual problem space is approx 4.2 billion squared, or 1.8e19 combinations.

    The performance of general library math operations like this needs to be amortized over the whole problem space. I'd be interested to see the results of a more normalized input distribution.

    0 讨论(0)
  • 2020-12-08 19:52

    I would guess that the majority of the added cost is in the set-up and tear-down of the static method call.

    As for why it exists, I would guess that it does in part for completeness and in part for the benefit of other languages that may not have easy to use implementations of integer division and modulus computation.

    0 讨论(0)
  • 2020-12-08 19:53

    Does anyone else get the opposite when testing this?

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

    FWIW, I'm running an Intel Core 2 Duo.

    The numbers above were with a debug build...

    With the release build:

    Math.DivRem = 10.314
    DivRem = 10.324
    MyDivRem = 5.380
    

    Looks like the "rem" IL command is less efficient than the "mul,sub" combo in MyDivRem.

    0 讨论(0)
  • 2020-12-08 19:56

    Wow, that really looks stupid, doesn't it?

    The problem is that -- according to the Microsoft Press book ".NET IL Assembler" by Lidin -- the IL rem and div atithmetic instructions are exactly that: compute remainder and compute divisor.

    All arithmetical operations except the negation operation take two operands from the stack and put the result on the stack.

    Apparently, the way the IL assembler language is designed, it's not possible to have an IL instruction that produces two outputs and pushes them onto the eval stack. Given that limitation, you can't have a division instruction in IL assembler that computes both the way the x86 DIV or IDIV instructions do.

    IL was designed for security, verifiability, and stability, NOT for performance. Anyone who has a compute-intensive application and is concerned primarily with performance will be using native code and not .NET.

    I recently attended Supercomputing '08, and in one of the technical sessions, an evangelist for Microsoft Compute Server gave the rough rule of thumb that .NET was usually half the speed of native code -- which is exactly the case here!.

    0 讨论(0)
提交回复
热议问题