Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/164.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

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.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++ 为什么除法3需要在x86上右移(和其他奇怪的事情)?_C++_Assembly_Compilation_X86 64_Integer Division - Fatal编程技术网

C++ 为什么除法3需要在x86上右移(和其他奇怪的事情)?

C++ 为什么除法3需要在x86上右移(和其他奇怪的事情)?,c++,assembly,compilation,x86-64,integer-division,C++,Assembly,Compilation,X86 64,Integer Division,我有以下C/C++函数: unsigned div3(unsigned x){ 返回x/3; } 在-O3,这将导致: div3(unsigned int): mov ecx, edi # tmp = x mov eax, 2863311531 # result = 3^-1 imul rax, rcx # result *= tmp shr rax, 33

我有以下C/C++函数:

unsigned div3(unsigned x){
返回x/3;
}
-O3
,这将导致:

div3(unsigned int):
        mov     ecx, edi         # tmp = x
        mov     eax, 2863311531  # result = 3^-1
        imul    rax, rcx         # result *= tmp
        shr     rax, 33          # result >>= 33
        ret
我真正理解的是:除以3等于乘以乘法逆3-1 mod 232,即2863311531

但有些事情我不明白:

  • 为什么我们需要使用
    ecx
    /
    rcx
    ?我们不能直接将
    rax
    edi
    相乘吗
  • 为什么我们要在64位模式下乘法?将
    eax
    ecx
    相乘不是更快吗
  • 为什么我们使用
    imul
    而不是
    mul
    ?我以为模运算都是无符号的
  • 最后的33位右移是怎么回事?我想我们可以去掉最高的32位
  • 编辑1 对于那些不明白我所说的3-1模232是什么意思的人,我在这里讨论的是乘法逆。 例如:

    //与3的倒数相乘:
    15 * 2863311531      = 42949672965
    42949672965模2^32=5
    //使用定点乘法
    15 * 2863311531      = 42949672965
    42949672965 >> 33    = 5
    //简单除以3
    15 / 3               = 5
    
    所以乘以42949672965实际上等于除以3。我假设clang的优化是基于模运算的,而实际上是基于定点运算的

    编辑2 我现在意识到乘法逆只能用于没有余数的除法。例如,1乘以3-1等于3-1,而不是零。只有定点算法具有正确的舍入

    不幸的是,clang没有使用模块化算法,在这种情况下,模块化算法只是一条
    imul
    指令,即使它可以。下面的函数具有与上面相同的编译输出

    unsigned div3(unsigned x){
    __内建假设(x%3==0);
    返回x/3;
    }
    

    (关于适用于所有可能输入的精确除法的定点乘法逆的规范问答:-不完全重复,因为它只涉及数学,而不涉及寄存器宽度和imul与mul等一些实现细节。)

    最后33位右移是怎么回事?我想我们可以去掉最高的32位

    3^(-1)mod 3
    不同,您必须更多地考虑
    0.3333333
    之前的
    0
    位于上32位,而
    3333
    位于下32位。 此定点操作工作正常,但结果明显移动到
    rax
    的上部,因此CPU必须在操作后再次向下移动结果

    为什么我们使用imul而不是mul?我以为模运算都是无符号的

    没有与
    IMUL
    指令等效的
    MUL
    指令。所使用的
    IMUL
    变体采用两个寄存器:

    a <= a * b
    
    a
    
  • 我们不能直接用edi乘以rax吗
  • 我们不能
    imulrax,rdi
    ,因为调用约定允许调用方在rdi的高位留下垃圾;只有EDI部分包含该值。这在内联时不是问题;写入32位寄存器不会隐式地将零扩展到完整的64位寄存器,因此编译器通常不需要额外的指令来对32位值进行零扩展

    (如果无法避免,零扩展到不同的寄存器会更好)

    更直截了当地说,x86没有任何乘法指令可以对其输入之一进行零扩展,从而使32位和64位寄存器相乘。两个输入的宽度必须相同

  • 为什么我们要在64位模式下乘法
  • (术语:所有这些代码都在64位模式下运行。您会问为什么64位操作数大小。)

    您可以
    mul-edi
    将EAX与edi相乘,以获得在EDX:EAX上拆分的64位结果,但是
    mul-edi
    在Intel CPU上是3个UOP,而大多数现代x86-64 CPU具有快速64位
    imul
    。(尽管imul r64,但在AMD推土机系列和一些低功耗CPU上,r64速度较慢。)和(指令表和Microach PDF) (有趣的事实:
    mul-rdi
    在英特尔CPU上实际上更便宜,只有2个UOP。也许是因为不必对整数乘法单元的输出进行额外的拆分,比如
    mul-edi
    就必须将64位低半乘法器输出拆分为EDX和EAX的一半,但对于64x64=>128位mul,这种情况自然发生。)

    另外,您需要的部件在EDX中,因此您需要另一个
    mov eax,EDX
    来处理它。(同样,因为我们正在查看函数的独立定义的代码,而不是在内联到调用方之后。)

    GCC 8.3和更早版本确实使用了32位
    mul
    而不是64位
    imul
    ()。当推土机系列和旧的Silvermont CPU更为相关时,这对于
    -mtune=generic
    来说并不疯狂,但这些CPU在过去对于较新的GCC来说更为重要,其通用调优选择反映了这一点。不幸的是,GCC还浪费了一条将EDI复制到EAX的
    mov
    指令,使这种方式看起来更糟:/

    # gcc8.3 -O3  (default -mtune=generic)
    div3(unsigned int):
            mov     eax, edi                 # 1 uop, stupid wasted instruction
            mov     edx, -1431655765         # 1 uop  (same 32-bit constant, just printed differently)
            mul     edx                      # 3 uops on Sandybridge-family
            mov     eax, edx                 # 1 uop
            shr     eax                      # 1 uop
            ret
                                      # total of 7 uops on SnB-family
    
    只有6个UOP具有
    mov eax、0xAAAAAB
    /
    mul edi
    ,但仍然比以下情况更糟:

    # gcc9.3 -O3  (default -mtune=generic)
    div3(unsigned int):
            mov     eax, edi                # 1 uop
            mov     edi, 2863311531         # 1 uop
            imul    rax, rdi                # 1 uop
            shr     rax, 33                 # 1 uop
            ret
                          # total 4 uops, not counting ret
    
    不幸的是,64位
    0x00000000aaaaab
    不能表示为32位符号扩展立即数,因此
    imul-rax、rcx、0xaaaaab
    不可编码。这意味着
    0xffffffffaaaaab

  • 为什么我们使用imul而不是mul?我以为模运算都是无符号的
  • 它没有签名。输入的符号性仅影响结果的高半部,但
    imul reg,reg
    不会产生高半部。只有一个操作数f
    // Warning: INEXACT FOR LARGE INPUTS
    // this fast approximation can just use the high half,
    // so on 32-bit machines it avoids one shift instruction vs. exact division
    int32_t div10(int32_t dividend)
    {
        int64_t invDivisor = 0x1999999A;
        return (int32_t) ((invDivisor * dividend) >> 32);
    }
    
    unsigned div3_exact_only(unsigned x) {
        __builtin_assume(x % 3 == 0);  // or an equivalent with if() __builtin_unreachable()
        return x / 3;
    }
    
    div3_exact_only:
        imul  eax, edi, 0xAAAAAAAB        # 1 uop, 3c latency
        ret
    
    uint32_t div3_exact_only(uint32_t x) {
        return x * 0xaaaaaaabU;
    }
    
    unsigned div7(unsigned x) {
        return x / 7;
    }
    
    mhi = (2^(32+L) + 2^(L))/3 = 5726623062
    mlo = (2^(32+L)        )/3 = 5726623061
    
    while((L > 0) && ((mhi>>1) > (mlo>>1))){
        mhi = mhi>>1;
        mlo = mlo>>1;
        L   = L-1;
    }
    if(mhi >= 2^32){
        mhi = mhi-2^32
        L   = L-1;
        ; use 3 additional instructions for missing 2^32 bit
    }
    ... mhi>>1 = 5726623062>>1 = 2863311531
    ... mlo>>1 = 5726623061>>1 = 2863311530  (mhi>>1) > (mlo>>1)
    ... mhi    = mhi>>1 = 2863311531
    ... mlo    = mhi>>1 = 2863311530
    ... L = L-1 = 1
    ... the next loop exits since now (mhi>>1) == (mlo>>1)
    
    L = ceil(log2(7)) = 3
    mhi = (2^(32+L) + 2^(L))/7 = 4908534053
    mhi = mhi-2^32 = 613566757
    L = L-1 = 2
    ...                 visual studio generated code for div7, input is rcx
    mov eax, 613566757
    mul ecx
    sub ecx, edx                   ; handle 2^32 bit
    shr ecx, 1                     ; ...
    lea eax, DWORD PTR [edx+ecx]   ; ...
    shr eax, 2
    
    mhi and L are generated based on divisor during compile time
    ...
    quotient  = (x*mhi)>>(32+L)
    product   = quotient*divisor
    remainder = x - product