Skip to content

Can "Math.DivRem" do division operation only once? #4155

Closed

Description

@ygc369

Currently it does division twice, but most CPUs support to get the two values by doing division only once.
Besides,this method should support uint and ulong too.

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

Activity

  1. jkotas commented on Apr 21, 2015

    @jkotas
    Member

    Math.DivRem is not in the .NET Core public surface, but I agree that it would be nice for the code generators to recognize the pattern and perform the division just one.

    cc @CarolEidt @cmckinsey

  2. stephentoub commented on Apr 21, 2015

    @stephentoub
    Member

    cc: @KrzysztofCwalina, as IIRC, Krzysztof bumped up against this as a measurable cost when doing some recent fomatting/parsing experimentation.

  3. CarolEidt commented on Apr 21, 2015

    @CarolEidt
    Contributor

    This would not be a very difficult pattern to recognize: a div and a rem with incoming value numbers that match, though it doesn't "fit" with any existing (or other common) optimization patterns. The thing that would be challenging would be to deal with the resulting IR node that would produce two values in registers. Although that would not be all that difficult, it would require more pervasive changes.

  4. tannergooding commented on Apr 21, 2015

    @tannergooding
    Member

    👍

    In short term, couldn't Math.DivRem be treated as a special-case (intrinsic)? I wouldn't think that this optimization wouldn't really require many changes.

    Long term though, adding an optimization pattern to recognize / and % with matching value members would be great.

  5. CarolEidt commented on Apr 21, 2015

    @CarolEidt
    Contributor

    @tannergooding - you would think that it would be simpler, but actually it requires the messiest part of the work (handling the node in the IR that produces two values), while only relieving the JIT of what is probably the more straightforward part of the implementation. It might actually be easier on x86 (32 bit) where we already have to deal with longs that occupy multiple registers.

  6. tannergooding commented on Jun 7, 2017

    @tannergooding
    Member

    @CarolEidt, I'm actually wondering why this would require more pervasive changes?

    I would think that instructions like idiv should already document (via some metadata) that they end up writing two registers, correct? As without this being carried around by the code generator, it would potentially end up overwriting the second argument of the parameter list whenever it generates the idiv instruction.

    I've not looked at the code, but I would guess that various instructions do indicate what registers end up being input/output by the instruction and that the changes here would be to allow the node to actually carry the second value (which could probably be done via inheritance and type checking). I actually think this is in a similar vein to having an intrinsic that takes 3 arguments where the GenTreeOp currently only allows 2 arguments.

  7. transferred this issue fromdotnet/coreclron Jan 30, 2020
  8. added this to the Future milestone on Jan 30, 2020
  9. ygc369 commented on Mar 29, 2020

    @ygc369
    Author

    @CarolEidt
    Is there any progress about this?

  10. ygc369 commented on Aug 26, 2020

    @ygc369
    Author

    @tannergooding @CarolEidt
    After more than five years, has this issue been solved?

  11. tannergooding commented on Aug 27, 2020

    @tannergooding
    Member

    No, but .NET 5 brought about some register allocator changes that should (hopefully) make it simpler to support now.

  12. caihongxu commented on Sep 21, 2020

    @caihongxu

    Before this is addressed in .NET 5, any remedies we could do ourselves. e.g. is it possible to roll our own DivRem method to achieve doing division only once?

  13. ygc369 commented on Feb 18, 2021

    @ygc369
    Author

    @tannergooding
    From this blog Announcing .NET 6 Preview 1, I see that there will be a new overload of DivRem for .Net 6, which returns a tuple. Will this new overload be able to do division only once?

  14. tannergooding commented on Feb 18, 2021

    @tannergooding
    Member

    The new API surface is setup in a way that would enable that optimization on x86/x64. I don't believe there is any instruction to compute quotient and remainder simultaneously on ARM32/ARM64.

    Whether or not it gets enabled likely depends on prioritization and availability from the dev's familiar enough with that area of the codebase.

  15. 5 remaining items

  16. modified the milestones: Future, 8.0.0 on Jan 17, 2023
  17. removed their assignment
    on Jan 28, 2023
  18. modified the milestones: 8.0.0, Future on Mar 25, 2023
  19. colejohnson66 commented on May 21, 2024

    @colejohnson66
    Contributor
  20. tannergooding commented on May 24, 2024

    @tannergooding
    Member

    DivRem has been implemented to do a division and then compute the remainder using multiplication for many releases now.

    On non-xarch platforms, this is the only way to compute the remainder as well, notably. There is no single instruction that computes div+rem simultaneously.

  21. colejohnson66 commented on May 24, 2024

    @colejohnson66
    Contributor

    Yes. So the main complaint that it performs two divisions is no longer valid.

  22. tannergooding commented on May 24, 2024

    @tannergooding
    Member

    And the overall history of the issue, which needs to be taken into account, covers that such a change went in. This issue then still exists to track the underlying spirit of the bug which is that its doing more work than necessary on some platforms.

  23. ygc369 commented on May 29, 2024

    @ygc369
    Author

    @tannergooding
    Using multiplication is also extra work, although maybe faster than an extra division. Isn't it possible to get div+rem at the same time just by doing division once and no other operation? Maybe this cannot be done for all platforms as some platforms do not support it by hardware. But there must be platforms that support it, so, can it be done just on supported platforms?

  24. bazzilic commented on May 29, 2024

    @bazzilic

    multiplication is also extra work, although maybe faster than an extra division

    it is indeed significantly faster

    can it be done just on supported platforms

    some comments above answer this question in great detail, e.g.: #4155 (comment)

  25. colejohnson66 commented on May 29, 2024

    @colejohnson66
    Contributor

    On x86, a division can take 15-30 clock cycles (sometimes over 40) and can only start one ever three or four. Multiplication, on the other hand, takes about eight and can start a new one every cycle.

  26. tannergooding commented on May 29, 2024

    @tannergooding
    Member

    Isn't it possible to get div+rem at the same time just by doing division once and no other operation

    For x86/x64, yes. It isn't possible on other platforms as they don't provide such support. However, even for x86/x64 the benefit there is really using 1 instruction, it isn't actually much faster (than doing div, mul, sub explicitly). In many cases it is actually more desirable to break it apart to avoid a dependency chain or use an alternative sequence (especially if one of the inputs is known constant).

    Modern CPUs (almost any in the past 12-14 years) account for these things and often have special div/idiv support that breaks it apart internally to what is functionally div, mul, sub already so that the quotient can be available prior to the remainder being available. They may even skip the internal microcode for computing the remainder in some cases if they can see its unused. The time is also often dependent on the number of significant bits in the dividend, although even newer processors have changed implementations and now it is often based on the number of significant bits in the quotient instead.

    On x86, a division can take 15-30 clock cycles (sometimes over 40) and can only start one ever three or four. Multiplication, on the other hand, takes about eight and can start a new one every cycle.

    Notably this very much depends on microarchitecture, whether you're doing signed or unsigned, and it has massively improved in even more recent years

    • Haswell (2013)
      • idiv (32-bit) took around 20-28 cycles with 8 reciprocal throughput
      • idiv (64-bit) took around 37-101 cycles with 24 reciprocal throughput.
    • Cannon Lake (2018)
      • idiv (32-bit) took around 10-15 cycles with 6 reciprocal throughput
      • idiv (64-bit) took around 14-18 cycles with 10 reciprocal throughput.

    Your multiplication info is also quite a bit off, both 32-bit and 64-bit have been 3 cycles with 1 reciprocal throughput since the early 2000s. If you're doing a big multiplication (64x64=128) then it used to take 7-9 or 3-10 cycles for pre 2011 chips, but that's not typical.

  27. locked and limited conversation to collaborators on Jun 30, 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