Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/ajax/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 特别地。最后,Mersenne5必须恢复esi和edi。在x64版本中,i的值直接传入ecx,因此根本不涉及内存访问。x64Mersenne5仅保存和恢复rsi,其他寄存器被阻塞_C#_.net_Performance_Compilation_Internal - Fatal编程技术网

C# 特别地。最后,Mersenne5必须恢复esi和edi。在x64版本中,i的值直接传入ecx,因此根本不涉及内存访问。x64Mersenne5仅保存和恢复rsi,其他寄存器被阻塞

C# 特别地。最后,Mersenne5必须恢复esi和edi。在x64版本中,i的值直接传入ecx,因此根本不涉及内存访问。x64Mersenne5仅保存和恢复rsi,其他寄存器被阻塞,c#,.net,performance,compilation,internal,C#,.net,Performance,Compilation,Internal,[x64 pro]x64Mersenne5中的指令要少得多 Mersenne5在x64中效率更高,因为它可以在单个指令中执行64位被除数上的所有操作,而mov和add/adc操作需要x86中的成对指令。我有一种预感,依赖链在x64中也更好,但我没有足够的知识来谈论这个问题 [x64 pro]在x64Mersenne5中有更好的跳转行为 Mersenne5最后执行的三个条件减法在x64下的实现要比x86好得多。在x86上,每一个都有两个比较和三个可能的条件跳转。在x64上,只有一个比较和一个条件跳

[x64 pro]x64
Mersenne5中的指令要少得多

Mersenne5
在x64中效率更高,因为它可以在单个指令中执行64位
被除数
上的所有操作,而
mov
add/adc
操作需要x86中的成对指令。我有一种预感,依赖链在x64中也更好,但我没有足够的知识来谈论这个问题

  • [x64 pro]在x64
    Mersenne5中有更好的跳转行为

    Mersenne5
    最后执行的三个条件减法在x64下的实现要比x86好得多。在x86上,每一个都有两个比较和三个可能的条件跳转。在x64上,只有一个比较和一个条件跳转,这无疑更有效


  • 考虑到这些要点,对于Ivy Bridge来说,我们可以看到从x86到x64的每个触发器的性能。64位除法延迟惩罚(在Ivy Bridge上比Broadwell稍差一点,但不会太大)可能会对
    RawModulo_5
    造成相当大的伤害,同时
    Mersenne5
    中的指令减半速度也在加快

    没有意义的是Broadwell上的结果——我仍然有点惊讶x64
    OptimizedModulo\u ViaMethod\u 5
    比x86
    RawModulo\u 5
    快了多少。我想答案应该是micro-op fusion和流水线,因为
    Mersenne5
    方法在x64上要好得多,或者您的体系结构上的JIT使用Broadwell特定的知识来输出非常不同的指令

    很抱歉,我不能给出一个更确切的答案,但我希望上面的分析能够启发我们,为什么这两种方法和两种体系结构之间存在差异

    顺便说一句,如果您想了解真正的内联版本可以做什么,请看:

    RawModulo_5, x86:                  13722506 ticks, 13.722506 ticks per iteration
    OptimizedModulo_ViaMethod_5, x86:  23640994 ticks, 23.640994 ticks per iteration
    OptimizedModulo_TrueInlined, x86:  21488012 ticks, 21.488012 ticks per iteration
    OptimizedModulo_TrueInlined2, x86: 21645697 ticks, 21.645697 ticks per iteration
    
    RawModulo_5, x64:                 22175326 ticks, 22.175326 ticks per iteration
    OptimizedModulo_ViaMethod_5, x64: 12822574 ticks, 12.822574 ticks per iteration
    OptimizedModulo_TrueInlined, x64:  7612328 ticks,  7.612328 ticks per iteration
    OptimizedModulo_TrueInlined2, x64: 7591190 ticks,  7.59119 ticks per iteration
    
    以及守则:

    public ulong OptimizedModulo_TrueInlined()
    {
        ulong r = 0;
        ulong dividend = 0;
    
        for (ulong i = 0; i < 1000; i++)
        {
            dividend = i;
            dividend = (dividend >> 32) + (dividend & 0xFFFFFFFF);
            dividend = (dividend >> 16) + (dividend & 0xFFFF);
            dividend = (dividend >> 8) + (dividend & 0xFF);
            dividend = (dividend >> 4) + (dividend & 0xF);
            dividend = (dividend >> 4) + (dividend & 0xF);
            if (dividend > 14) { dividend = dividend - 15; } // mod 15
            if (dividend > 10) { dividend = dividend - 10; }
            if (dividend > 4) { dividend = dividend - 5; }
            r += dividend;
        }
        return r;
    }
    
    public ulong OptimizedModulo_TrueInlined2()
    {
        ulong r = 0;
        ulong dividend = 0;
    
        for (ulong i = 0; i < 1000; i++)
        {
            dividend = (i >> 32) + (i & 0xFFFFFFFF);
            dividend = (dividend >> 16) + (dividend & 0xFFFF);
            dividend = (dividend >> 8) + (dividend & 0xFF);
            dividend = (dividend >> 4) + (dividend & 0xF);
            dividend = (dividend >> 4) + (dividend & 0xF);
            if (dividend > 14) { dividend = dividend - 15; } // mod 15
            if (dividend > 10) { dividend = dividend - 10; }
            if (dividend > 4) { dividend = dividend - 5; }
            r += dividend;
        }
        return r;
    }
    
    public-ulong-OptimizedModulo_-TrueInlined()
    {
    乌隆r=0;
    乌龙股息=0;
    对于(ulong i=0;i<1000;i++)
    {
    股息=i;
    股息=(股息>>32)+(股息&0xFFFFFFFF);
    股息=(股息>>16)+(股息和0xFFFF);
    股息=(股息>>8)+(股息&0xFF);
    股息=(股息>>4)+(股息&0xF);
    股息=(股息>>4)+(股息&0xF);
    如果(股息>14){股息=股息-15;}//mod 15
    如果(股息>10){股息=股息-10;}
    如果(股息>4){股息=股息-5;}
    r+=股息;
    }
    返回r;
    }
    公共ulong优化modulo_TrueInlined2()
    {
    乌隆r=0;
    乌龙股息=0;
    对于(ulong i=0;i<1000;i++)
    {
    股息=(i>>32)+(i&0xFFFFFFFF);
    股息=(股息>>16)+(股息和0xFFFF);
    股息=(股息>>8)+(股息&0xFF);
    股息=(股息>>4)+(股息&0xF);
    股息=(股息>>4)+(股息&0xF);
    如果(股息>14){股息=股息-15;}//mod 15
    如果(股息>10){股息=股息-10;}
    如果(股息>4){股息=股息-5;}
    r+=股息;
    }
    返回r;
    }
    
    这是代码片段中的瓶颈语句,@ozeanix也解释了这一点。我将对他详尽的答案进行注释

    除法是处理器必须执行的硬操作之一,没有已知的数字电路可以在单个周期内执行除法。它必须用迭代的方法来实现,与你在小学学到的方法没有根本的不同。执行时间与位数成正比,64位除法的速度预计是32位除法的两倍

    x86抖动必须生成笨重的代码才能用32位寄存器进行计算,因此在
    ulong
    的上32位为0的情况下,它采用了一种快捷方式。在这个特殊的例子中,999和5足够小了。请注意,64位代码在Mersenne5()方法上的速度要快得多,能够使用单个寄存器存储中间值,使用单个移位指令一次移动64位,这给了它很大的提升

    x64抖动不能使用与x86抖动相同的技巧,如果不降低代码的速度,64位寄存器的高32位就不能直接寻址。这并不意味着你会被较慢的性能所困扰,有足够的信心让任何一头猪都能飞起来。我将展示一个从C编译器优化器反向工程的编码技巧。它在这种特定情况下有效,因为您重复使用相同的除数。为了说明这个技巧,这是这样一个编译器在其内部循环中生成的机器代码,循环展开和指令混合被移除:

    00007FF603121006  mov         rax,0CCCCCCCCCCCCCCCDh    ; magic!
    00007FF603121010  mul         rax,r9                    ; magic * i
    00007FF603121013  shr         rdx,2                     ; rdx = (magic * i) / 4 / 2^64 
    00007FF603121017  lea         rcx,[rdx+rdx*4]           ; 5 * rdx
    00007FF60312101B  mov         rdx,r9                    ; i
    00007FF60312101E  sub         rdx,rcx                   ; i - 5 * ((magic * i) / 4 / 2^64)
    00007FF603121024  add         r8,rdx                    ; r += i % 5
    
    这是咳嗽,很难理解。关键的一点是,代码根本不使用DIV指令,但可以使用SHR,这使得它非常快。SHR与C#中的
    >
    运算符完全等价,右移等于除以2的幂

    最大的技巧是将5的除法转换为2的幂的除法。这在一般情况下是不可能的,但可以进行近似计算。需要一些重写技巧才能看到这一点。它从将模转换为除的标识开始:

    A % B == A - B * (A / B)
    
    通过将左侧和右侧相乘来变换除法
    RawModulo_5, x86:                 13721978 ticks, 13.721978 ticks per iteration
    OptimizedModulo_ViaMethod_5, x86: 24641039 ticks, 24.641039 ticks per iteration
    
    RawModulo_5, x64:                 23275799 ticks, 23.275799 ticks per iteration
    OptimizedModulo_ViaMethod_5, x64: 13389012 ticks, 13.389012 ticks per iteration
    
    static public ulong Mersenne5(ulong dividend)
    {
        dividend = (dividend >> 32) + (dividend & 0xFFFFFFFF);
        dividend = (dividend >> 16) + (dividend & 0xFFFF);
        dividend = (dividend >> 8) + (dividend & 0xFF);
        dividend = (dividend >> 4) + (dividend & 0xF);
        // there was an extra shift by 4 here
        if (dividend > 14) { dividend = dividend - 15; } // mod 15
        // the 9 used to be a 10
        if (dividend > 9) { dividend = dividend - 10; }
        if (dividend > 4) { dividend = dividend - 5; }
        return dividend;
    }
    
                System.Diagnostics.Debugger.Break();
    00242DA2  in          al,dx  
    00242DA3  push        edi  
    00242DA4  push        ebx  
    00242DA5  sub         esp,10h  
    00242DA8  call        6D4C0178  
                ulong r = 0;
    00242DAD  mov         dword ptr [ebp-10h],0  ; setting the low and high dwords of 'r'
    00242DB4  mov         dword ptr [ebp-0Ch],0  
                for (ulong i = 0; i < 1000; i++)
    ; set the high dword of 'i' to 0
    00242DBB  mov         dword ptr [ebp-14h],0
    ; clear the low dword of 'i' to 0 - the compiler is using 'edi' as the loop iteration var
    00242DC2  xor         edi,edi  
                {
                    r += i % 5;
    00242DC4  mov         eax,edi  
    00242DC6  mov         edx,dword ptr [ebp-14h]  
    ; edx:eax together are the high and low dwords of 'i', respectively
    
    ; this is a short circuit trick so it can avoid working with the high
    ; dword - you can see it jumps halfway in to the div/mod operation below
    00242DC9  mov         ecx,5  
    00242DCE  cmp         edx,ecx  
    00242DD0  jb          00242DDC  
    ; 64 bit div/mod operation
    00242DD2  mov         ebx,eax  
    00242DD4  mov         eax,edx  
    00242DD6  xor         edx,edx  
    00242DD8  div         eax,ecx  
    00242DDA  mov         eax,ebx  
    00242DDC  div         eax,ecx  
    00242DDE  mov         eax,edx  
    00242DE0  xor         edx,edx
    ; load the current low and high dwords from 'r', then add into
    ; edx:eax as a pair forming a qword
    00242DE2  add         eax,dword ptr [ebp-10h]  
    00242DE5  adc         edx,dword ptr [ebp-0Ch]  
    ; store the result back in 'r'
    00242DE8  mov         dword ptr [ebp-10h],eax  
    00242DEB  mov         dword ptr [ebp-0Ch],edx  
                for (ulong i = 0; i < 1000; i++)
    ; load the loop variable low and high dwords into edx:eax
    00242DEE  mov         eax,edi  
    00242DF0  mov         edx,dword ptr [ebp-14h]  
    ; increment eax (the low dword) and propagate any carries to
    ; edx (the high dword)
    00242DF3  add         eax,1  
    00242DF6  adc         edx,0  
    ; store the low and high dwords back to the high word of 'i' and
    ; the loop iteration counter, 'edi'
    00242DF9  mov         dword ptr [ebp-14h],edx  
    00242DFC  mov         edi,eax
    ; test the high dword  
    00242DFE  cmp         dword ptr [ebp-14h],0  
    00242E02  ja          00242E0E  
    00242E04  jb          00242DC4  
    ; (int) i < 1000
    00242E06  cmp         edi,3E8h  
    00242E0C  jb          00242DC4  
                }
                return r;
    ; retrieve the current value of 'r' from memory, return value is
    ; in edx:eax since the return value is 64 bits
    00242E0E  mov         eax,dword ptr [ebp-10h]  
    00242E11  mov         edx,dword ptr [ebp-0Ch]  
    00242E14  lea         esp,[ebp-8]  
    00242E17  pop         ebx  
    00242E18  pop         edi  
    00242E19  pop         ebp  
    00242E1A  ret  
    
                System.Diagnostics.Debugger.Break();
    00242E33  push        edi  
    00242E34  push        esi  
    00242E35  push        ebx  
    00242E36  sub         esp,8  
    00242E39  call        6D4C0178  
                ulong r = 0;
    ; same as above, initialize 'r' to zero using low and high dwords
    00242E3E  mov         dword ptr [ebp-10h],0  
    ; this time we're using edi:esi as the loop counter, rather than
    ; edi and a memory location. probably less register pressure in this
    ; function, for reasons we'll see...
    00242E45  xor         ebx,ebx  
                for (ulong i = 0; i < 1000; i++)
    ; initialize 'i' to 0, esi is the loop counter low dword, edi is the high dword
    00242E47  xor         esi,esi  
    00242E49  xor         edi,edi  
    ; push 'i' to the stack, high word then low word
    00242E4B  push        edi  
    00242E4C  push        esi  
    ; call Mersenne5 - it got put in the data section since it's static
    00242E4D  call        dword ptr ds:[3D7830h]  
    ; return value comes back as edx:eax, where edx is the high dword
    ; ebx is the existing low dword of 'r', so it's accumulated into eax
    00242E53  add         eax,ebx  
    ; the high dword of 'r' is at ebp-10, that gets accumulated to edx with
    ; the carry result of the last add since it's 64 bits wide
    00242E55  adc         edx,dword ptr [ebp-10h]
    ; store edx:ebx back to 'r'  
    00242E58  mov         dword ptr [ebp-10h],edx  
    00242E5B  mov         ebx,eax  
    ; increment the loop counter and carry to edi as well, 64 bit add
    00242E5D  add         esi,1  
    00242E60  adc         edi,0  
    ; make sure edi == 0 since it's the high dword
    00242E63  test        edi,edi  
    00242E65  ja          00242E71  
    00242E67  jb          00242E4B  
    ; (int) i < 1000
    00242E69  cmp         esi,3E8h  
    00242E6F  jb          00242E4B  
                }
                return r;
    ; move 'r' to edx:eax to return them
    00242E71  mov         eax,ebx  
    00242E73  mov         edx,dword ptr [ebp-10h]  
    00242E76  lea         esp,[ebp-0Ch]  
    00242E79  pop         ebx  
    00242E7A  pop         esi  
    00242E7B  pop         edi  
    00242E7C  pop         ebp  
    00242E7D  ret  
    
                System.Diagnostics.Debugger.Break();
    00342E92  in          al,dx  
    00342E93  push        edi  
    00342E94  push        esi  
    ; esi is the low dword, edi is the high dword of the 64 bit argument
    00342E95  mov         esi,dword ptr [ebp+8]  
    00342E98  mov         edi,dword ptr [ebp+0Ch]  
    00342E9B  call        6D4C0178  
                dividend = (dividend >> 32) + (dividend & 0xFFFFFFFF);
    ; this is a LOT of instructions for each step, but at least it's all registers.
    
    ; copy edi:esi to edx:eax
    00342EA0  mov         eax,esi  
    00342EA2  mov         edx,edi
    ; clobber eax with edx, so now both are the high word. this is a
    ; shorthand for a 32 bit shift right of a 64 bit number.  
    00342EA4  mov         eax,edx
    ; clear the high word now that we've moved the high word to the low word  
    00342EA6  xor         edx,edx
    ; clear the high word of the original 'dividend', same as masking the low 32 bits  
    00342EA8  xor         edi,edi  
    ; (dividend >> 32) + (dividend & 0xFFFFFFFF)
    ; it's a 64 bit add, so it's the usual add/adc
    00342EAA  add         eax,esi  
    00342EAC  adc         edx,edi
    ; 'dividend' now equals the temporary "variable" that held the addition result  
    00342EAE  mov         esi,eax  
    00342EB0  mov         edi,edx  
                dividend = (dividend >> 16) + (dividend & 0xFFFF);
    ; same idea as above, but with an actual shift and mask since it's not 32 bits wide
    00342EB2  mov         eax,esi  
    00342EB4  mov         edx,edi  
    00342EB6  shrd        eax,edx,10h  
    00342EBA  shr         edx,10h  
    00342EBD  and         esi,0FFFFh  
    00342EC3  xor         edi,edi  
    00342EC5  add         eax,esi  
    00342EC7  adc         edx,edi  
    00342EC9  mov         esi,eax  
    00342ECB  mov         edi,edx  
                dividend = (dividend >> 8) + (dividend & 0xFF);
    ; same idea, keep going down...
    00342ECD  mov         eax,esi  
    00342ECF  mov         edx,edi  
    00342ED1  shrd        eax,edx,8  
    00342ED5  shr         edx,8  
    00342ED8  and         esi,0FFh  
    00342EDE  xor         edi,edi  
    00342EE0  add         eax,esi  
    00342EE2  adc         edx,edi  
    00342EE4  mov         esi,eax  
    00342EE6  mov         edi,edx  
                dividend = (dividend >> 4) + (dividend & 0xF);
    00342EE8  mov         eax,esi  
    00342EEA  mov         edx,edi  
    00342EEC  shrd        eax,edx,4  
    00342EF0  shr         edx,4  
    00342EF3  and         esi,0Fh  
    00342EF6  xor         edi,edi  
    00342EF8  add         eax,esi  
    00342EFA  adc         edx,edi  
    00342EFC  mov         esi,eax  
    00342EFE  mov         edi,edx  
                dividend = (dividend >> 4) + (dividend & 0xF);
    00342F00  mov         eax,esi  
    00342F02  mov         edx,edi  
    00342F04  shrd        eax,edx,4  
    00342F08  shr         edx,4  
    00342F0B  and         esi,0Fh  
    00342F0E  xor         edi,edi  
    00342F10  add         eax,esi  
    00342F12  adc         edx,edi  
    00342F14  mov         esi,eax  
    00342F16  mov         edi,edx  
                if (dividend > 14) { dividend = dividend - 15; } // mod 15
    ; conditional subtraction
    00342F18  test        edi,edi  
    00342F1A  ja          00342F23  
    00342F1C  jb          00342F29  
    ; 'dividend' > 14
    00342F1E  cmp         esi,0Eh  
    00342F21  jbe         00342F29  
    ; 'dividend' = 'dividend' - 15
    00342F23  sub         esi,0Fh 
    ; subtraction borrow from high word 
    00342F26  sbb         edi,0  
                if (dividend > 10) { dividend = dividend - 10; }
    ; same gist for the next two
    00342F29  test        edi,edi  
    00342F2B  ja          00342F34  
    00342F2D  jb          00342F3A  
    00342F2F  cmp         esi,0Ah  
    00342F32  jbe         00342F3A  
    00342F34  sub         esi,0Ah  
    00342F37  sbb         edi,0  
                if (dividend > 4) { dividend = dividend - 5; }
    00342F3A  test        edi,edi  
    00342F3C  ja          00342F45  
    00342F3E  jb          00342F4B  
    00342F40  cmp         esi,4  
    00342F43  jbe         00342F4B  
    00342F45  sub         esi,5  
    00342F48  sbb         edi,0  
                return dividend;
    ; move edi:esi into edx:eax for return
    00342F4B  mov         eax,esi  
    00342F4D  mov         edx,edi  
    00342F4F  pop         esi  
    00342F50  pop         edi  
    00342F51  pop         ebp  
    00342F52  ret         8  
    
                System.Diagnostics.Debugger.Break();
    000007FE98C93CF0  sub         rsp,28h  
    000007FE98C93CF4  call        000007FEF7B079C0  
                ulong r = 0;
    ; the compiler knows the high dword of rcx is already 0, so it just
    ; zeros the low dword. this is 'r'
    000007FE98C93CF9  xor         ecx,ecx  
                for (ulong i = 0; i < 1000; i++)
    ; same here, this is 'i'
    000007FE98C93CFB  xor         r8d,r8d  
                {
                    r += i % 5;
    ; load 5 as a dword to the low dword of r9
    000007FE98C93CFE  mov         r9d,5  
    ; copy the loop counter to rax for the div below
    000007FE98C93D04  mov         rax,r8  
    ; clear the lower dword of rdx, upper dword is clear already
    000007FE98C93D07  xor         edx,edx  
    ; 64 bit div/mod in one instruction! but it's slow!
    000007FE98C93D09  div         rax,r9  
    ; rax = quotient, rdx = remainder
    ; throw away the quotient since we're just doing mod, and accumulate the
    ; modulus into 'r'
    000007FE98C93D0C  add         rcx,rdx  
                for (ulong i = 0; i < 1000; i++)
    ; 64 bit increment to the loop counter
    000007FE98C93D0F  inc         r8  
    ; i < 1000
    000007FE98C93D12  cmp         r8,3E8h  
    000007FE98C93D19  jb          000007FE98C93CFE  
                }
                return r;
    ; return 'r' in rax, since we can directly return a 64 bit var in one register now
    000007FE98C93D1B  mov         rax,rcx  
    000007FE98C93D1E  add         rsp,28h  
    000007FE98C93D22  ret  
    
                System.Diagnostics.Debugger.Break();
    000007FE98C94040  push        rdi  
    000007FE98C94041  push        rsi  
    000007FE98C94042  sub         rsp,28h  
    000007FE98C94046  call        000007FEF7B079C0  
                ulong r = 0;
    ; same general loop setup as above
    000007FE98C9404B  xor         esi,esi  
                for (ulong i = 0; i < 1000; i++)
    ; 'edi' is the loop counter
    000007FE98C9404D  xor         edi,edi  
    ; put rdi in rcx, which is the x64 register used for the first argument
    ; in a call
    000007FE98C9404F  mov         rcx,rdi  
    ; call Mersenne5 - still no actual inlining!
    000007FE98C94052  call        000007FE98C90F40  
    ; accumulate 'r' with the return value of Mersenne5
    000007FE98C94057  add         rax,rsi  
    ; store back to 'r' - I don't know why in the world the compiler did this
    ; seems like add rsi, rax would be better, but maybe there's a pipelining
    ; issue I'm not seeing.
    000007FE98C9405A  mov         rsi,rax  
    ; increment loop counter
    000007FE98C9405D  inc         rdi  
    ; i < 1000
    000007FE98C94060  cmp         rdi,3E8h  
    000007FE98C94067  jb          000007FE98C9404F  
                }
                return r;
    ; put return value in rax like before
    000007FE98C94069  mov         rax,rsi  
    000007FE98C9406C  add         rsp,28h  
    000007FE98C94070  pop         rsi  
    000007FE98C94071  pop         rdi  
    000007FE98C94072  ret  
    
                System.Diagnostics.Debugger.Break();
    000007FE98C94580  push        rsi  
    000007FE98C94581  sub         rsp,20h  
    000007FE98C94585  mov         rsi,rcx  
    000007FE98C94588  call        000007FEF7B079C0  
                dividend = (dividend >> 32) + (dividend & 0xFFFFFFFF);
    ; pretty similar to before actually, except this time we do a real
    ; shift and mask for the 32 bit part
    000007FE98C9458D  mov         rax,rsi  
    ; 'dividend' >> 32
    000007FE98C94590  shr         rax,20h  
    ; hilariously, we have to load the mask into edx first. this is because
    ; there is no AND r/64, imm64 in x64
    000007FE98C94594  mov         edx,0FFFFFFFFh  
    000007FE98C94599  and         rsi,rdx  
    ; add the shift and the masked versions together
    000007FE98C9459C  add         rax,rsi  
    000007FE98C9459F  mov         rsi,rax  
                dividend = (dividend >> 16) + (dividend & 0xFFFF);
    ; same logic continues down
    000007FE98C945A2  mov         rax,rsi  
    000007FE98C945A5  shr         rax,10h  
    000007FE98C945A9  mov         rdx,rsi  
    000007FE98C945AC  and         rdx,0FFFFh  
    000007FE98C945B3  add         rax,rdx  
    
    ; note the redundant moves that happen every time, rax into rsi, rsi
    ; into rax. so there's still not ideal x64 being generated.
    000007FE98C945B6  mov         rsi,rax  
                dividend = (dividend >> 8) + (dividend & 0xFF);
    000007FE98C945B9  mov         rax,rsi  
    000007FE98C945BC  shr         rax,8  
    000007FE98C945C0  mov         rdx,rsi  
    000007FE98C945C3  and         rdx,0FFh  
    000007FE98C945CA  add         rax,rdx  
    000007FE98C945CD  mov         rsi,rax  
                dividend = (dividend >> 4) + (dividend & 0xF);
    000007FE98C945D0  mov         rax,rsi  
    000007FE98C945D3  shr         rax,4  
    000007FE98C945D7  mov         rdx,rsi  
    000007FE98C945DA  and         rdx,0Fh  
    000007FE98C945DE  add         rax,rdx  
    000007FE98C945E1  mov         rsi,rax  
                dividend = (dividend >> 4) + (dividend & 0xF);
    000007FE98C945E4  mov         rax,rsi  
    000007FE98C945E7  shr         rax,4  
    000007FE98C945EB  mov         rdx,rsi  
    000007FE98C945EE  and         rdx,0Fh  
    000007FE98C945F2  add         rax,rdx  
    000007FE98C945F5  mov         rsi,rax  
                if (dividend > 14) { dividend = dividend - 15; } // mod 15
    ; notice the difference in jumping logic - the pairs of jumps are now singles
    000007FE98C945F8  cmp         rsi,0Eh  
    000007FE98C945FC  jbe         000007FE98C94602 
    ; using a single 64 bit add instead of a subtract, the immediate constant
    ; is the 2's complement of 15. this is okay because there's no borrowing
    ; to do since we can do the entire sub in one operation to one register. 
    000007FE98C945FE  add         rsi,0FFFFFFFFFFFFFFF1h  
                if (dividend > 10) { dividend = dividend - 10; }
    000007FE98C94602  cmp         rsi,0Ah  
    000007FE98C94606  jbe         000007FE98C9460C  
    000007FE98C94608  add         rsi,0FFFFFFFFFFFFFFF6h  
                if (dividend > 4) { dividend = dividend - 5; }
    000007FE98C9460C  cmp         rsi,4  
    000007FE98C94610  jbe         000007FE98C94616  
    000007FE98C94612  add         rsi,0FFFFFFFFFFFFFFFBh  
                return dividend;
    000007FE98C94616  mov         rax,rsi  
    000007FE98C94619  add         rsp,20h  
    000007FE98C9461D  pop         rsi  
    000007FE98C9461E  ret  
    
    RawModulo_5, x86:                  13722506 ticks, 13.722506 ticks per iteration
    OptimizedModulo_ViaMethod_5, x86:  23640994 ticks, 23.640994 ticks per iteration
    OptimizedModulo_TrueInlined, x86:  21488012 ticks, 21.488012 ticks per iteration
    OptimizedModulo_TrueInlined2, x86: 21645697 ticks, 21.645697 ticks per iteration
    
    RawModulo_5, x64:                 22175326 ticks, 22.175326 ticks per iteration
    OptimizedModulo_ViaMethod_5, x64: 12822574 ticks, 12.822574 ticks per iteration
    OptimizedModulo_TrueInlined, x64:  7612328 ticks,  7.612328 ticks per iteration
    OptimizedModulo_TrueInlined2, x64: 7591190 ticks,  7.59119 ticks per iteration
    
    public ulong OptimizedModulo_TrueInlined()
    {
        ulong r = 0;
        ulong dividend = 0;
    
        for (ulong i = 0; i < 1000; i++)
        {
            dividend = i;
            dividend = (dividend >> 32) + (dividend & 0xFFFFFFFF);
            dividend = (dividend >> 16) + (dividend & 0xFFFF);
            dividend = (dividend >> 8) + (dividend & 0xFF);
            dividend = (dividend >> 4) + (dividend & 0xF);
            dividend = (dividend >> 4) + (dividend & 0xF);
            if (dividend > 14) { dividend = dividend - 15; } // mod 15
            if (dividend > 10) { dividend = dividend - 10; }
            if (dividend > 4) { dividend = dividend - 5; }
            r += dividend;
        }
        return r;
    }
    
    public ulong OptimizedModulo_TrueInlined2()
    {
        ulong r = 0;
        ulong dividend = 0;
    
        for (ulong i = 0; i < 1000; i++)
        {
            dividend = (i >> 32) + (i & 0xFFFFFFFF);
            dividend = (dividend >> 16) + (dividend & 0xFFFF);
            dividend = (dividend >> 8) + (dividend & 0xFF);
            dividend = (dividend >> 4) + (dividend & 0xF);
            dividend = (dividend >> 4) + (dividend & 0xF);
            if (dividend > 14) { dividend = dividend - 15; } // mod 15
            if (dividend > 10) { dividend = dividend - 10; }
            if (dividend > 4) { dividend = dividend - 5; }
            r += dividend;
        }
        return r;
    }
    
      r += i % 5;
    
    00007FF603121006  mov         rax,0CCCCCCCCCCCCCCCDh    ; magic!
    00007FF603121010  mul         rax,r9                    ; magic * i
    00007FF603121013  shr         rdx,2                     ; rdx = (magic * i) / 4 / 2^64 
    00007FF603121017  lea         rcx,[rdx+rdx*4]           ; 5 * rdx
    00007FF60312101B  mov         rdx,r9                    ; i
    00007FF60312101E  sub         rdx,rcx                   ; i - 5 * ((magic * i) / 4 / 2^64)
    00007FF603121024  add         r8,rdx                    ; r += i % 5
    
    A % B == A - B * (A / B)
    
    A % B == A - B * ((A * N / B) / N)
    
    A % B ~= A - B * (A * K / N)   where K ~= N / B
    
    public class FastModulo {
        public FastModulo(ulong maxdividend, ulong divisor) {
            div = divisor;
            int dividendbits = 1 + (int)(Math.Log(maxdividend - 1) / Math.Log(2));
            shift = 64 - dividendbits;
            mult = (ulong)Math.Round((double)(1UL << shift) / divisor);
            //TODO: verify that the approximation is accurate enough.
        }
        public ulong Modulo(ulong value) {
            return value - (div * ((value * mult) >> shift));
        }
        int shift;
        ulong mult, div;
    }
    
    public ulong RawModulo_5() {
        var fm = new FastModulo(1000, 5);
        ulong r = 0;
        for (uint i = 0; i < 1000; i++) {
            r += fm.Modulo(i);
        }
    }
    
            r += i - (5 * ((i * 3602879701896397UL) >> 54));