ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

放弃所谓“右移优化除法”行为

2022-04-24 17:00:53  阅读:220  来源: 互联网

标签:右移 mov eax 操作 除法 优化 移位


The Difference between Division and Arithmetic Right Shifting in C

你是否有听说过有符号数不能使用右移操作(>>)来代替除法? 这篇短文会向你证明它,并尝试向你解释为什么。当然,如果你没有听说过,那么从现在开始,记住它!

Foundation: Logical Shift .vs. Arithmetic Shift

若你现在有二进制数x=1110B,对其施加右移操作,请问高位填0还是填1?

逻辑移位不管造成的影响,总是用0来填充移位操作产生的空缺。但是这样简单的想法在一些情况总会出错。例如若上述x是有符号数,那么简单的填0就会造成错误,起码正负号出错了。

算数移位支持有符号数的移位操作,在移位后使用符号位进行填充,结合补码的表示方法,就能实现正确的负数移位操作。

总结来说:在有符号的场景下,使用算数位移;如果你能保证移位操作是无符号的,那么用逻辑位移也无妨.

x86汇编代码中,shr代表逻辑右移指令,sar代表算数右移指令,我们可以通过以下C代码及其反汇编的结果来更好的理解逻辑移位和算数移位:

#include <stdlib.h>
#include <stdio.h>
    
signed int x = -3;
unsigned int y = 3;

int main()
{
    x >>= 1;
    y >>= 1;
    return 0;
}

x:
        .long   -3
y:
        .long   3
main:
        push    rbp
        mov     rbp, rsp
        mov     eax, DWORD PTR x[rip]
        sar     eax
        mov     DWORD PTR x[rip], eax
        mov     eax, DWORD PTR y[rip]
        shr     eax
        mov     DWORD PTR y[rip], eax
        mov     eax, 0
        pop     rbp
        ret

https://godbolt.org/z/K4M4Ko4c7

Demo to Prove the Subject

在我作为一个初级程序员的认知中,/2>>1是等价的,甚至一起还听说过后者能够优化代码的效率。但是今天我要告诉你, Definitely wrong!

或许在遥远的古代,我们使用位移操作真的能够对代码进行加速,但是当下编译器已经足够聪明,如果你真的动手反汇编"/2"的代码,那么你就会知道编译器已经替你优化为了位移操作。

更糟糕的是,我们要避免使用移位操作来实现除法或者乘法,不仅仅是因为这两者等价,实际上,他们并不是等价的!并且会造成错误!

Let me show you a little demo.

考虑如下的C语言代码:

#include <stdlib.h>
#include <stdio.h>

signed int x = -3;
signed int y = -3;

int main()
{
    x >>= 1;
    y /= 2;
    return 0;
}

他们的汇编代码是相同的吗?这里还是拿X86汇编举例:

; Following is ‘x >>= 1’
mov     eax, #-3  ;x
sar     eax
mov     x, eax
; Following is ‘y/= 2’
mov     eax, #-3  ;y
mov     edx, eax
shr     edx, 31
add     eax, edx
sar     eax
mov     y, eax

注意:以上的汇编代码省去了一些我认为无关紧要的操作,并不是完全正确的,但是足够表达他们的差别了。

可以看出,除法比移位多了一步shr edx, 31过程,下面会探讨这个。

还有一件使你震惊的事件,x, y的值最终是不同的!是的,正是因为那条看似“多余”的shr指令。

Why does This happen?

首先,我们可以确定的一件事是:编译器真的帮我们将除法操作优化为移位。所以,再也不要说你的代码中使用>>来替代除法是为了增加执行效率了。

让我们来解释下为什么两者的结果是不同的。

首先,sar指令在x86指令集中表示算数右移,这个是我们熟悉的,那么-3进行算数右移后的结果就是-2. 意味着>>是向负无穷舍入的.

那么除法操作又是在干什么呢? 它是将原值加上其符号位.Demo中使用的数据类型是32位int.

shr     edx, 31
add     eax, edx

这样做必然改变了原值啊,动手算一下就会知道,-3/2的结果为-1. 并且只有负奇数会受影响,对于正数,其符号为0;对于负偶数,其补码的最低位必为0,刚加上的1会被下一步的算数右移丢弃,不对高位产生影响。

Aha, 差别就是向负无穷舍弃还是向0舍弃,一时间竟然不知道哪个是正确的了。

What Should We Do?

根据最新的[C语言标准草案](ISO/IEC 9899:201x (open-std.org)) 6.5.7章节,负数的右移操作是implementation-defined,即取决于具体的实现:

The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

因此,理论上它依赖于实现。所以我们在实际应用中为了程序的可移植性,应当避免对有符号数使用移位操作。除非你能确定它的值一定是非负数,在此情况下,请将它用无符号类型来声明。

对于除法操作,标准中的6.5.5章节规定了,除法操作总是向0舍入. 非常好!

When integers are divided, the result of the / operator is the algebraic quotient with any fractional part discarded.

检查你的代码,恢复所有的“优化”乘除法的行为吧!

标签:右移,mov,eax,操作,除法,优化,移位
来源: https://www.cnblogs.com/bluettt/p/16186598.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有