首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  程序员

大家工作中对位操作的使用是什么态度

  •  
  •   lihongjie0209 · 49 天前 · 5120 次点击
    这是一个创建于 49 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有些时候确实挺方便的, 但是不那么"直观"(对于团队中的其他人)

    大家的看法是什么

    76 回复  |  直到 2019-08-01 10:48:24 +08:00
        1
    jaskle   49 天前 via Android
    非密集计算用位操作我决定扇死他,我现在翻翻以前写的 c 代码都想扇自己,为了省内存各种可读性的降低,后期维护加功能麻烦的一批!
        2
    invoke   49 天前   ♥ 14
    写的时候觉得挺牛逼的。

    维护的时候觉得挺傻逼的。
        3
    jaskle   49 天前 via Android
    以前害怕 int 占用大,布尔型用 uchar 的 0 和 1,最后知道真相的我眼泪掉下来
        4
    des   49 天前 via Android   ♥ 2
    一般封装了再使用,也还行
        5
    qq976739120   49 天前
    出了刷题,我还没在工作中使用过位运算,业务代码里用位运算要么被实习生崇拜,要么被队友喷
        6
    zqx   49 天前 via Android
    比如 !!~a.indexOf(b)算吗
        7
    lihongjie0209   49 天前
    @qq976739120 #5 最近也是刷题的时候用的比较多, 项目上刚好有个场景可以使用就打算上, 结果领导说"不直观", 看来确实如此, 少用为好
        8
    across   49 天前
    封装成一个 enum + class····
    不然确实不直观。
        9
    lihongjie0209   49 天前
    @across #8 嗯 谢谢建议
        10
    ljzxloaf   49 天前 via iPhone
    Bitset
        11
    lihongjie0209   49 天前
    @ljzxloaf #10 一般情况下直接用 int/long, 除非你的状态超过了 32/64 种
        12
    loginbygoogle   49 天前
    能不用就不用
        13
    ssynhtn   49 天前 via Android
    现实中没见过谁用的,只有 Android 源码里面会用
        14
    zartouch   49 天前   ♥ 1
    我们用的很多

    主要是数据量大 ( 100G - 200G jvm heap ), 可以省内存。
    二是系统要求延迟尽可能低,所以很多操作时间复杂度要尽量优化。

    除非系统对性能没有要求否则我很难想象不需要位操作。
        15
    lihongjie0209   49 天前
    @zartouch #14 可读性和性能之间的权衡
        16
    mason961125   49 天前 via iPhone
    嵌入式 /单片机 各种通信协议不用位运算麻烦死你。(微笑
        17
    LeeSeoung   49 天前
    如果是变动不大 要求性能高的,特别是算法实现的 用位操作是非常合适的。。如果是普通业务功能,拖出去打~
        18
    maichael   49 天前
    除非必要,能不用就用。
        19
    lihongjie0209   49 天前
    @mason961125 #16 毕竟比较底层
        20
    q397064399   49 天前   ♥ 3
    过早优化是万恶之源
        21
    winterfell30   49 天前
    @jaskle 求教,char 不能省空间吗
        22
    orzorzorzorz   49 天前
    最多用简单的运算,比如 ~~ 之类的
        23
    littlewing   49 天前 via iPhone
    @winterfell30 如果你这个 char 在 struct 里的话,因为字节对其的原因,很可能对齐到 4 字节上去了
        24
    GeruzoniAnsasu   49 天前
    难到你们写的东西都不需要

    status = STAT_A | STAT_B ?
        25
    liuxey   49 天前
    除了底层软件,操作系统,数据库,驱动等,应用层用位操作就是作死
        26
    coolair   49 天前
    铁定不用啊,过一天自己都看不明白是啥意思。
        27
    jaskle   49 天前 via Android   ♥ 1
    @winterfell30
    目前 cpu 单周期最小 32 位,所以为了提高存取效率单个 uchar 会占用 4 个字节,也就是 int 大小。当然可以使用紧缩型编译,但会导致一个 uchar 会先读取整合相邻 4 字节,然后通过移位拿出属于他的 1 字节 uchar。所以读取周期会是 2 个,写入就更麻烦了……
    这也就是我们常说的 4 字节对齐,非 4 字节对齐会极大耗费 cpu,一般来讲编译器将所有变量的起始地址都对齐 4 字节,这么来讲一个 uchar 和 int 内存开销是一致的,但 uchar 运算开销倍增!
        28
    Raymon111111   49 天前
    ...

    没什么必要就别用了

    又优化不了多少性能
        29
    winterfell30   49 天前
    @jaskle 好的多谢,目前项目中有一个对内存要求很高的地方就是用 char 存的 int 类型,然后用 pack(1)的方法强制了 1 个字节对齐,CPU 这块还没有评估
        30
    xuanbg   49 天前
    多种状态复合判断的时候,位操作挺好使的。
        31
    rayhy   49 天前 via Android
    用位操作算 flag 不是蛮常用的吗?
        32
    summer20100514   49 天前 via Android   ♥ 2
    果然 v 站都是纯程序员不搞嵌入式
        33
    gamexg   49 天前
    @GeruzoniAnsasu #24 +1
    状态、错误代码不用位运算?
    另外实现二进制协议也需要位运算。
        34
    Takamine   49 天前 via Android
    什么,你说 hashmap,hash 一致性算法用位运算?好,我知道了,那就直接取个模。:doge:
        35
    kaminic   49 天前 via Android
    大小端转换
    颜色值顺序转换 比如 rgba 转 abgr
    用位操作简单快速
    所以还是看需求吧,位操作不是洪水猛兽
        36
    ysn2233   49 天前   ♥ 1
    >>这种还是经常用的
        37
    OhYee   49 天前   ♥ 1
    我觉得我就是你们要喷的用位运算的。
    可是用位运算真的解决了很多后期的问题,而且我封装好并且写了注释

    个人认为该用的话还是有必要用的,可读性可以靠注释和封装来弥补。
        38
    AlvaIM   49 天前   ♥ 4
    现在的年轻人怎么啦,基础的东西学不会还则罢了, 居然学不会还喷。
        39
    socradi   49 天前
    位运算在某些地方还是蛮方便的,比如读二进制文件。可以不用位运算的地方就尽量不用,可读性不太好。
        40
    iwtbauh   49 天前 via Android
    @jaskle #3

    那你可能需要看看<<迷失的 C 结构打包艺术>>: https://github.com/ludx/The-Lost-Art-of-C-Structure-Packing/blob/master/README.md
        41
    iwtbauh   49 天前 via Android
    @mason961125 #16

    为什么不用位域呢
        42
    mason961125   49 天前
    @iwtbauh #41 读 I/O 电平存一个字节为啥要用位域?
        43
    iwtbauh   49 天前 via Android   ♥ 3
    @jaskle #27

    不对。对齐的目的是防止跨越对齐边界读。

    比如你使用的指令是 32 位操作指令。你必须使内存地址为 32 位的倍数。否则可能出现 3 种情况:

    1. 你的 CPU 支持非对齐访问(如 Intel 家族)。这时性能会降低。
    2. 你的 CPU 不支持非对齐房屋,但编译器发现你在这么做,于是编译器将代码展开成两次读取,然后用位运算得出正确结果。性能降低。
    3. 你的 CPU 不支持非对齐访问,同时因为你的写法的原因(考虑到使用 void *指针倒了一次编译器已经没法理解了),编译器不会采取额外操作,运行时,你的程序触发总线错误。

    而 1 字节的 char 不会使用 32 位的读取指令读取,用的是 8 位操作的指令,没有位运算,它也不会用两个 CPU 周期。性能没有损失。而且,因为能更高效的利用 CPU 高速缓存,实际上性能会更好!对于编译器,除非有必要,编译器尽可能不把 char 扩充。
        44
    iwtbauh   49 天前 via Android   ♥ 1
    @mason961125 #42

    #16 “各种通信协议不用位运算麻烦死你”。

    比如向总线发送 5 位的 a,10 位 b,15 位 c,6 自己校验和。这时候用位域的话就不用自己手写位运算填充了呀。
        45
    iwtbauh   49 天前 via Android
    乘 /除 2 的倍数时,我习惯写成移位

    如果需要操作某种二进制协议 /文件格式时,我优先使用位域,但有时也会使用位运算

    如果只是为了省内存,除非是极端情况(硬件条件极其恶劣或者运算强度需要压榨出机器最后一丝性能)拒绝使用。
        46
    iwtbauh   49 天前 via Android
    @iwtbauh #45 额,不是 2 的倍数,2^n,这叫什么,是幂吗,数学渣渣哭了
        47
    muzhidianzi   49 天前 via Android
    @jaskle 小白求大佬展开讲讲?还一直没思考过这个
        48
    muzhidianzi   49 天前 via Android
    @jaskle 才发现楼下有解释 尴尬 下次耐心看完再提问 多谢大佬点出问题所在
        49
    jaskle   49 天前 via Android
    @iwtbauh 准确的说 x86 确实有读取和写入单字节的指令,不过这并不代表他是个单周期指令,退 1w 步讲,内存条数据总线单周期最小读取是 4 个字节,当然不排除 cpu 缓存的存在。在单片机之类的环境表现尤为突出,当年使用某国产指纹芯片 as605 不对齐 cpu 竟然直接异常。st 系列好很多,但是在遇到跨页读取(通过强转读取通讯数据 buf 的 4 字节)仍然会出现读取数据错误(这个问题查了很久)。
    当然这个话题并不是为了抬杠,而是想说明 uchar 作为布尔是没有任何意义的,所占用空间(编译器强制 4 对齐)和计算时间(≥1 个周期)都没有任何优势。
        50
    jaskle   49 天前 via Android
    对于位操作我的想法是看计算类型以及计算量,主要考虑到可读性和开发效率。如果对位计算有兴趣可以阅读一下 bitmap 算法相关书籍
        51
    ttgo   49 天前
    偶尔用一下,炫技。。
        52
    iwtbauh   49 天前 via Android
    @jaskle #49

    “所占用空间(编译器强制 4 对齐)和计算时间(≥1 个周期)都没有任何优势。”

    看#43:

    “对于编译器,除非有必要,编译器尽可能不把 char 扩充。” 除非是没办法了,编译器是有多想不开才会把 char 对齐到 4 字节啊。

    “它也不会用两个 CPU 周期。性能没有损失。而且,因为能更高效的利用 CPU 高速缓存,实际上性能会更好” 我反正没有见过哪个现代处理器是违反了这一条的,可能有很罕见的处理器上不一样吧。

    不对齐访问异常很正常,比如 sun spark 就是这样的。但是不同的数据类型对齐要求不一样,char 没有对其要求,16 位的 short 要求按照 2 字节对齐,32 位的 int 要求 4 字节对齐,64 位的 long 要求 8 字节对齐。
        53
    szq8014   49 天前
    @jaskle 哈哈哈哈
        54
    monsterxx03   49 天前
    二进制协议解析肯定要用,比如之前写 dns 协议 parser 的时候.
    bitmap 和相关变种算法里很常用, 比如 bloom filter, 你得先实现个 bit array 吧.
        55
    momocraft   49 天前
    如果语义一致(比如可 | & 复合) 用位运算是最自然的
        56
    Chowe   49 天前
    对不起我不仅用位运算我还各种 goto 外加直接访问物理内存:doge
        57
    inhzus   49 天前 via Android
    Golang 没有 enum 用位运算表示类型很正常吧…
        58
    Harv   49 天前
    开发时简单运算顺手就用,但一般是发布调试前统一改,统一优化。
    至于你们说后期维护...一般都会把原式注释放后面的。如果不带注释,读起来是真的伤身体。
        59
    junbaor   49 天前
    代码是给人看的,顺便让机器执行一下。如果不是基础框架,并且性能瓶颈不在那一块,建议直接打死。😃
        60
    AlphaTr   49 天前
    不抵触,不滥用;优先保证语义的清晰;然后才考虑性能
        61
    wangyaominde   49 天前
    之前写嵌入式,还是位操作好用,如果为了维护,还是要尽量写好注释
        62
    pmispig   49 天前
    嵌入式一般都是位操作,节省内存和流量带宽
        63
    karllynn   49 天前
    单片机写过没,老铁
        64
    jaskle   49 天前 via Android
    @iwtbauh emmmm,其实你可以连续定义 2 个 uchar 然后断点,用&拿出地址,看看是不是 4 字节对齐
        65
    iwtbauh   49 天前   ♥ 2
    @jaskle #64

    编译器:
    gcc (Debian 8.3.0-6) 8.3.0
    Copyright (C) 2018 Free Software Foundation, Inc.

    源码:
    #include <stdio.h>

    int main()
    {
    unsigned char a;
    unsigned char b;

    unsigned short c;
    unsigned short d;

    unsigned long e;

    printf("%p, %p\n", &a, &b);
    printf("%p, %p\n", &c, &d);
    printf("%p\n", &e);

    return 0;
    }

    构建目标:x86_64-pc-linux-gnu
    构建指令:gcc -fno-pie -no-pie -Wall -O3 test.c

    运行:
    ./a.out
    0x7ffc780f27d2, 0x7ffc780f27d3
    0x7ffc780f27d4, 0x7ffc780f27d6
    0x7ffc780f27d8

    uchar 没有对齐
    ushort 按 2 字节对其
    ulong 按 8 字节对其
        66
    RayeGong   49 天前
    @invoke 真理 后期需求修改 review 的时候简直觉得自己脑积水
        67
    imycc   49 天前 via iPhone
    可以写,但是你得在位运算上面留一段注释说明意图跟原理。这也适用于其他为了让程序更高效而逻辑不直观的地方。
        68
    shawndev   49 天前
    Clean Code 一书最受启发的一点:不要把抽象层级不同的代码放在一起。

    比如 IO 操作和报文校验。正则表达式和 rpc 调用。

    基于这个共识,位操作能用就用。
        69
    toma77   49 天前
    写权限系统的时候用位运算比较好
        70
    jaskle   48 天前 via Android
    @iwtbauh 我刚刚试了一下 win 下也确实没有对齐,只有单片机是 4 对齐的,估计还是与 cup 指令集有关系。
    如果申请> 1 字节数组的话会强制对齐 4 字节。
    感谢。
        71
    xxdd   48 天前
    项目中没必要 维护成本远远大于省下的性能成本。
        72
    Bown   48 天前
    BLE 开发必备,传输速度太慢了,不自定义二进制协议用户没法用
        73
    spadger   48 天前
    位域了解下。
        74
    ShawyerPeng   48 天前
    判断某个状态是否存在的场景使用位运算不是挺常见的吗,比如订单状态的枚举值分别有:1-已取消(OrderStatusEnum.Canceled),2-已下单,4-待处理,8-已支付,16-待出行,32-已成交。新增订单某个状态位的时候,只需要进行异或运算 orderStatus |= OrderStatusEnum.XXX ;删除某个状态位时只需要 orderStatus ^= OrderStatusEnum.XXX ;判断是否存在某个状态时,只需要用(orderStatus & OrderStatusEnum.XXX) ==0 判断即可。
        75
    metrxqin   48 天前 via Android
    对 2^N 求模:x & (2^N - 1)
    乘或者整除 2^N:X < N 或者 X > N
    如果数值集合为{0, 2, 4, N } n = 2^x, x=1, 2, 3...
    则可以使用一个字节表达 256 种数值,假设这些数值被表示用于分配堆内存大小( Buddy Allocator) 这只需要一个字节 X 便足以表达任意大小(最大不超过 2^255)(代表 2 的幂),执行 2 < X 还原真实空间大小。

    这样看来还有有点用处的。
        76
    nodwang   48 天前
    @iwtbauh 实践是检验真理的唯一标准,看这个贴子让我有一种做过山车的感觉, 哦学到了->额,不对->哦学到了->诶,还不对,实践出真知,感谢
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4106 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 40ms · UTC 09:05 · PVG 17:05 · LAX 02:05 · JFK 05:05
    ♥ Do have faith in what you're doing.