Skip to content

[RyuJIT] Div then Mod (or viceversa) executes idiv twice instead of using the value in the register #5213

Open

Description

@redknightlois

Found this trying to optimize a very low level memory addressing algorithm. Suddenly I noticed that we were doing the idiv twice.

Repro on rc2-16551:

  public class Program
    {
        public long Dividend;

        [MethodImpl(MethodImplOptions.NoInlining)]
        public long Run(long parameter)
        {
            long div = parameter / Dividend;
            long mod = parameter % Dividend;

            return div + mod;
        }

        public static void Main(string[] args)
        {
            Random r = new Random();

            var p = new Program();
            p.Dividend = 10;

            long value = p.Run(203);
            Console.WriteLine($"{value}");
            Console.ReadLine();
        }
    }

will output (in x64 but will also happen in x86):

00007FF8E8EC3C83  mov         rcx,qword ptr [rcx+8]  
00007FF8E8EC3C87  mov         rax,r8          // 2 instructions to prepare for idiv. 
00007FF8E8EC3C8A  cqo                    
00007FF8E8EC3C8C  idiv        rax,rcx         // parameter / Dividend
00007FF8E8EC3C8F  mov         r9,rax          // r9:= divResult 
00007FF8E8EC3C92  mov         rax,r8          // 2 instructions to prepare for idiv. 
00007FF8E8EC3C95  cqo  
00007FF8E8EC3C97  idiv        rax,rcx         //  parameter / Dividend
00007FF8E8EC3C9A  lea         rax,[r9+rdx]    // rax := divResult + modulus

this could be written as:

00007FF8E8EC3C83  mov         rcx,qword ptr [rcx+8]  
00007FF8E8EC3C87  mov         rax,r8          // 2 instructions to prepare for idiv. 
00007FF8E8EC3C8A  cqo                    
00007FF8E8EC3C8C  idiv        rax,rcx         //  parameter / Dividend
00007FF8E8EC3C92  add         rax,rdx         //  rax := divResult + modulus

This is pretty common in addressing modes or accessing matrix locations, etc. Everywhere you have a row and a column, you will have such an structure.

category:cq
theme:div-mod-rem
skill-level:expert
cost:large
impact:medium

Activity

  1. mikedn commented on Mar 1, 2016

    @mikedn
    Contributor

    Related to #4155

  2. mikedn commented on Mar 1, 2016

    @mikedn
    Contributor

    This is pretty common in addressing modes or accessing matrix locations, etc. Everywhere you have a row and a column, you will have such an structure.

    Div and mod have nothing to do with addressing, that's always done with add and mul. You're probably talking about something else.

  3. jakobbotsch commented on Mar 1, 2016

    @jakobbotsch
    Member

    Div and mod have nothing to do with addressing, that's always done with add and mul. You're probably talking about something else.

    Div and mod are pretty useful to recover row/column information from indices:

    int index = row * width + col;
    ...
    int row = index / width;
    int col = index % width;
    

    Mostly when you are treating a singular array as a 2D array (eg. through usage of Bitmap.LockBits).
    That said, it's not very common, but not extremely rare either.

  4. redknightlois commented on Mar 1, 2016

    @redknightlois
    Author

    @mikedn It is if you are working with stride based binary structures as @JanielS mentioned. I would mention too, that when you are dealing with building data structures using memory maps (therefore unsafe code), it is a pretty common thing to happen.

  5. redknightlois commented on Mar 1, 2016

    @redknightlois
    Author

    @mikedn Actually this is a duplicate of #4155. I would have never though about calling it DivRem that's why I havent found it.

    @CarolEidt Is the limitation of using multiple registers per single instruction still relevant?

  6. CarolEidt commented on Mar 1, 2016

    @CarolEidt
    Contributor

    Yes, the multiple register issue is still relevant, but likely something we will soon be addressing.

  7. mikedn commented on Mar 1, 2016

    @mikedn
    Contributor

    It is if you are working with stride based binary structures as @JanielS mentioned.

    What @JanielS shows is not so common and certainly not related to the notion of "addressing modes" as used in compilers. More generally, accessing matrix elements has nothing to do with division and modulo. The offset of a matrix element is simply row * stride + column. Sure, there may be special/unusual/whatever cases that requires division and modulo but it's a long way from such cases to "Everywhere you have a row and a column, you will have such an structure.".

  8. redknightlois commented on Mar 1, 2016

    @redknightlois
    Author

    @mikedn They are just two sides of the same coin.

    AbsolutePosition := row * stride + column
    

    and

    row := AbsolutePosition / TotalColumns
    column = AbsolutePosition % TotalColumns
    

    Putting all in the context of the assembly/compiler use is why you are seeing it as special/unusual, where it is not. This pattern is usually hidden in plain sight, it is so common and used, that is very easy to overlook. Only in certain places you will see both together because that means you are trying to retrieve both values at the same time instead of accessing it using a for loop.

  9. redknightlois commented on Nov 11, 2016

    @redknightlois
    Author

    Any update on if we are going to see this anytime soon?

    1

    And the code that generates it.

    1 1

    This is actual code I cannot get away with, I will probably try to tweak it to reduce dependencies, but that's it. This is killing me, if I can get even 5% would be a huge win. :)

    EDIT: All the program in context (marked in red address translations - div/mod heavy

    1 2
    )

    cc @AndyAyersMS

  10. mikedn commented on Nov 11, 2016

    @mikedn
    Contributor

    Does BlocksPerPage change often? I would guess that it doesn't, it's fixed for a given BTree. If that's the case then you could use reciprocal division, it would likely be better than the JIT removing a redundant idiv.

  11. redknightlois commented on Nov 11, 2016

    @redknightlois
    Author

    @mikedn That's a great idea, I can probably pack 8 to 10 multiplications before paying for an idiv even more with 2 of them, I can do almost whatever I want and still probably win. Do you have any good resource on how to efficiently deal with a "sorta" fixed (but not constant) divisor?

  12. mikedn commented on Nov 12, 2016

    @mikedn
    Contributor

    Before eliminating division you should eliminate the reminder. n % d is simply n - (n / d) * d. You already have n / d so to compute % you only need a - and a * and these are very cheap compared to %. Come to think of it, the JIT could probably do this automatically if it turns out that removing the redundant idiv is problematic due to the multiple register issue.

    Now, getting rid of the division completely may or may not be feasible. For a given denominator you have to store 2 numbers somewhere - a "magic" number and a shift count. Computing these 2 numbers is expensive so if you can't store them somewhere this won't work.

    The exacts details can be found here, in the "Exact division by constants" section. To make your life easier you could simply pick up existing code from a compiler. RyuJIT has an implementation and I extracted it here.

    It's not as fast as I hoped but it's still ~1.5x faster than division. The code generated by the compiler when the denominator is actually a constant is faster (3x) but that's expected, for example that code doesn't have to deal with positive/negative magic numbers at runtime.

    Note that this implementation only works for positive numbers, supporting negative numbers makes MagicDiv more complicated and probably slower.

  13. redknightlois commented on Nov 12, 2016

    @redknightlois
    Author

    @mikedn I can do that on tree creation and just store it and be done with it :).

  14. stephentoub commented on Nov 14, 2016

    @stephentoub
    Member

    Before eliminating division you should eliminate the reminder. n % d is simply n - (n / d) * d. You already have n / d so to compute % you only need a - and a * and these are very cheap compared to %. Come to think of it, the JIT could probably do this automatically if it turns out that removing the redundant idiv is problematic due to the multiple register issue.

    Until there's a solution for this in the JIT, should:
    https://github.com/dotnet/coreclr/blob/f8b8b6ab80f1c5b30cc04676ca2e084ce200161e/src/mscorlib/src/System/Math.cs#L733
    be changed from:

    public static long DivRem(long a, long b, out long result)
    {
        result = a % b;
        return a / b;
    }

    to:

    public static long DivRem(long a, long b, out long result)
    {
        long div = a / b;
        result = a - (div * b);
        return div;
    }

    ? (And the same for the Int32 overload.)

  15. redknightlois commented on Nov 14, 2016

    @redknightlois
    Author

    @stephentoub Yes, it should. I can confirm that the performance of using the multiplication in the second case (in my case) has been huge.

  16. 16 remaining items

  17. svick commented on Sep 26, 2022

    @svick
    Member

    @pentp Would RuntimeHelpers.IsKnownConstant help with that?

  18. pentp commented on Sep 26, 2022

    @pentp
    Contributor

    Probably yes, but for long/ulong needs #67285 (or part of it).

  19. removed
    JitUntriagedCLR JIT issues needing additional triage
    on Oct 31, 2022
  20. self-assigned this
    on Oct 31, 2022
  21. modified the milestones: Future, 8.0.0 on Jan 17, 2023
  22. TIHan commented on Jan 28, 2023

    @TIHan
    Contributor

    Related: #4155

  23. removed their assignment
    on Jan 28, 2023
  24. added a commit that references this issue on Feb 12, 2023
  25. modified the milestones: 8.0.0, Future on Jun 9, 2023
  26. added a commit that references this issue on Apr 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIenhancementProduct code improvement that does NOT require public API changes/additionsoptimizationtenet-performancePerformance related issue

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions