大家工作中对位操作的使用是什么态度 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
lihongjie0209
V2EX    程序员

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

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

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

    大家的看法是什么

    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
    能不用就不用
    kx5d62Jn1J9MjoXP
        13
    kx5d62Jn1J9MjoXP  
       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/div>
    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
    div class="reply_content">@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
    现在的年轻人怎么啦,基础的东西学不会还则罢了, 居然学不会还喷。
    qfpZ2KhNsF23UGbN
        39
    qfpZ2KhNsF23UGbN  
       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     Solana     910 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 56ms UTC 21:43 PVG 05:43 LAX 13:43 JFK 16:43
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86