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

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

  •  
  •   lihongjie0209 · 2019-07-30 18:31:50 +08:00 · 7182 次点击
    这是一个创建于 1703 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    大家的看法是什么

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

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

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

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

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

    没什么必要就别用了

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

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

    那你可能需要看看<<迷失的 C 结构打包艺术>>: https://github.com/ludx/The-Lost-Art-of-C-Structure-Packing/blob/master/README.md
    iwtbauh
        41
    iwtbauh  
       2019-07-30 23:40:45 +08:00 via Android
    @mason961125 #16

    为什么不用位域呢
    mason961125
        42
    mason961125  
       2019-07-30 23:46:34 +08:00
    @iwtbauh #41 读 I/O 电平存一个字节为啥要用位域?
    iwtbauh
        43
    iwtbauh  
       2019-07-30 23:50:51 +08:00 via Android   ❤️ 3
    @jaskle #27

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

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

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

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

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

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

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

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

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

    看#43:

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

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

    不对齐访问异常很正常,比如 sun spark 就是这样的。但是不同的数据类型对齐要求不一样,char 没有对其要求,16 位的 short 要求按照 2 字节对齐,32 位的 int 要求 4 字节对齐,64 位的 long 要求 8 字节对齐。
    szq8014
        53
    szq8014  
       2019-07-31 09:09:37 +08:00
    @jaskle 哈哈哈哈
    monsterxx03
        54
    monsterxx03  
       2019-07-31 09:29:04 +08:00
    二进制协议解析肯定要用,比如之前写 dns 协议 parser 的时候.
    bitmap 和相关变种算法里很常用, 比如 bloom filter, 你得先实现个 bit array 吧.
    momocraft
        55
    momocraft  
       2019-07-31 09:32:15 +08:00
    如果语义一致(比如可 | & 复合) 用位运算是最自然的
    Chowe
        56
    Chowe  
       2019-07-31 09:48:27 +08:00
    对不起我不仅用位运算我还各种 goto 外加直接访问物理内存:doge
    inhzus
        57
    inhzus  
       2019-07-31 09:49:06 +08:00 via Android
    Golang 没有 enum 用位运算表示类型很正常吧…
    Harv
        58
    Harv  
       2019-07-31 10:21:44 +08:00
    开发时简单运算顺手就用,但一般是发布调试前统一改,统一优化。
    至于你们说后期维护...一般都会把原式注释放后面的。如果不带注释,读起来是真的伤身体。
    junbaor
        59
    junbaor  
       2019-07-31 10:24:34 +08:00
    代码是给人看的,顺便让机器执行一下。如果不是基础框架,并且性能瓶颈不在那一块,建议直接打死。😃
    AlphaTr
        60
    AlphaTr  
       2019-07-31 10:50:44 +08:00
    不抵触,不滥用;优先保证语义的清晰;然后才考虑性能
    wangyaominde
        61
    wangyaominde  
       2019-07-31 11:38:11 +08:00
    之前写嵌入式,还是位操作好用,如果为了维护,还是要尽量写好注释
    pmispig
        62
    pmispig  
       2019-07-31 11:52:37 +08:00
    嵌入式一般都是位操作,节省内存和流量带宽
    karllynn
        63
    karllynn  
       2019-07-31 11:55:16 +08:00
    单片机写过没,老铁
    jaskle
        64
    jaskle  
       2019-07-31 12:10:31 +08:00 via Android
    @iwtbauh emmmm,其实你可以连续定义 2 个 uchar 然后断点,用&拿出地址,看看是不是 4 字节对齐
    iwtbauh
        65
    iwtbauh  
       2019-07-31 12:52:13 +08:00   ❤️ 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 字节对其
    RayeGong
        66
    RayeGong  
       2019-07-31 13:48:00 +08:00
    @invoke 真理 后期需求修改 review 的时候简直觉得自己脑积水
    imycc
        67
    imycc  
       2019-07-31 13:57:44 +08:00 via iPhone
    可以写,但是你得在位运算上面留一段注释说明意图跟原理。这也适用于其他为了让程序更高效而逻辑不直观的地方。
    shawndev
        68
    shawndev  
       2019-07-31 16:13:30 +08:00
    Clean Code 一书最受启发的一点:不要把抽象层级不同的代码放在一起。

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

    基于这个共识,位操作能用就用。
    toma77
        69
    toma77  
       2019-07-31 16:23:14 +08:00
    写权限系统的时候用位运算比较好
    jaskle
        70
    jaskle  
       2019-07-31 17:46:46 +08:00 via Android
    @iwtbauh 我刚刚试了一下 win 下也确实没有对齐,只有单片机是 4 对齐的,估计还是与 cup 指令集有关系。
    如果申请> 1 字节数组的话会强制对齐 4 字节。
    感谢。
    xxdd
        71
    xxdd  
       2019-07-31 18:10:57 +08:00
    项目中没必要 维护成本远远大于省下的性能成本。
    Bown
        72
    Bown  
       2019-07-31 19:15:27 +08:00
    BLE 开发必备,传输速度太慢了,不自定义二进制协议用户没法用
    spadger
        73
    spadger  
       2019-07-31 19:58:47 +08:00
    位域了解下。
    ShawyerPeng
        74
    ShawyerPeng  
       2019-07-31 22:04:24 +08:00
    判断某个状态是否存在的场景使用位运算不是挺常见的吗,比如订单状态的枚举值分别有:1-已取消(OrderStatusEnum.Canceled),2-已下单,4-待处理,8-已支付,16-待出行,32-已成交。新增订单某个状态位的时候,只需要进行异或运算 orderStatus |= OrderStatusEnum.XXX ;删除某个状态位时只需要 orderStatus ^= OrderStatusEnum.XXX ;判断是否存在某个状态时,只需要用(orderStatus & OrderStatusEnum.XXX) ==0 判断即可。
    metrxqin
        75
    metrxqin  
       2019-07-31 23:49:00 +08:00 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 还原真实空间大小。

    这样看来还有有点用处的。
    nodwang
        76
    nodwang  
       2019-08-01 10:48:24 +08:00
    @iwtbauh 实践是检验真理的唯一标准,看这个贴子让我有一种做过山车的感觉, 哦学到了->额,不对->哦学到了->诶,还不对,实践出真知,感谢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1130 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 18:44 · PVG 02:44 · LAX 11:44 · JFK 14:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.