
有些时候确实挺方便的, 但是不那么"直观"(对于团队中的其他人)
大家的看法是什么
1 jaskle 2019-07-30 18:41:17 +08:00 via Android 非密集计算用位操作我决定扇死他,我现在翻翻以前写的 c 代码都想扇自己,为了省内存各种可读性的降低,后期维护加功能麻烦的一批! |
2 invoke 2019-07-30 18:42:28 +08:00 写的时候觉得挺牛逼的。 维护的时候觉得挺傻逼的。 |
3 jaskle 2019-07-30 18:43:05 +08:00 via Android 以前害怕 int 占用大,布尔型用 uchar 的 0 和 1,最后知道真相的我眼泪掉下来 |
4 des 2019-07-30 18:46:56 +08:00 via Android 一般封装了再使用,也还行 |
5 qq976739120 2019-07-30 18:49:37 +08:00 出了刷题,我还没在工作中使用过位运算,业务代码里用位运算要么被实习生崇拜,要么被队友喷 |
6 zqx 2019-07-30 18:50:20 +08:00 via Android 比如 !!~a.indexOf(b)算吗 |
7 lihongjie0209 OP @qq976739120 #5 最近也是刷题的时候用的比较多, 项目上刚好有个场景可以使用就打算上, 结果领导说"不直观", 看来确实如此, 少用为好 |
8 across 2019-07-30 18:52:01 +08:00 封装成一个 enum + class 不然确实不直观。 |
9 lihongjie0209 OP @across #8 嗯 谢谢建议 |
10 ljzxloaf 2019-07-30 18:55:52 +08:00 via iPhone Bitset |
11 lihongjie0209 OP @ljzxloaf #10 一般情况下直接用 int/long, 除非你的状态超过了 32/64 种 |
12 loginbygoogle 2019-07-30 18:58:47 +08:00 能不用就不用 |
13 kx5d62Jn1J9MjoXP 2019-07-30 19:23:57 +08:00 via Android 现实中没见过谁用的,只有 Android 源码里面会用 |
14 zartouch 2019-07-30 19:30:42 +08:00 我们用的很多 主要是数据量大 ( 100G - 200G jvm heap ), 可以省内存。 二是系统要求延迟尽可能低,所以很多操作时间复杂度要尽量优化。 除非系统对性能没有要求否则我很难想象不需要位操作。 |
15 lihongjie0209 OP @zartouch #14 可读性和性能之间的权衡 |
16 mason961125 2019-07-30 19:32:33 +08:00 via iPhone 嵌入式 /单片机 各种通信协议不用位运算麻烦死你。(微笑 |
17 LeeSeoung 2019-07-30 19:33:23 +08:00 如果是变动不大 要求性能高的,特别是算法实现的 用位操作是非常合适的。。如果是普通业务功能,拖出去打~ |
18 maichael 2019-07-30 19:34:51 +08:00 除非必要,能不用就用。 |
19 lihongjie0209 OP @mason961125 #16 毕竟比较底层 |
20 q397064399 2019-07-30 19:41:28 +08:00 过早优化是万恶之源 |
21 winterfell30 2019-07-30 19:42:51 +08:00 @jaskle 求教,char 不能省空间吗 |
23 littlewing 2019-07-30 19:45:42 +08:00 via iPhone @winterfell30 如果你这个 char 在 struct 里的话,因为字节对其的原因,很可能对齐到 4 字节上去了 |
24 GeruzoniAnsasu 2019-07-30 19:46:20 +08:00 难到你们写的东西都不需要 status = STAT_A | STAT_B ? |
25 liuxey 2019-07-30 19:46:54 +08:00 除了底层软件,操作系统,数据库,驱动等,应用层用位操作就是作死 |
26 coolair 2019-07-30 20:06:37 +08:00 铁定不用啊,过一天自己都看不明白是啥意思。 |
27 jaskle 2019-07-30 20:22:54 +08:00 via Android @winterfell30 目前 cpu 单周期最小 32 位,所以为了提高存取效率单个 uchar 会占用 4 个字节,也就是 int 大小。当然可以使用紧缩型编译,但会导致一个 uchar 会先读取整合相邻 4 字节,然后通过移位拿出属于他的 1 字节 uchar。所以读取周期会是 2 个,写入就更麻烦了…… 这也就是我们常说的 4 字节对齐,非 4 字节对齐会极大耗费 cpu,一般来讲编译器将所有变量的起始地址都对齐 4 字节,这么来讲一个 uchar 和 int 内存开销是一致的,但 uchar 运算开销倍增! |
28 Raymon111111 2019-07-30 20:28:48 +08:00 ... 没什么必要就别用了 又优化不了多少性能 |
29 winterfell30 2019-07-30 20:42:31 +08:00 div class="reply_content">@jaskle 好的多谢,目前项目中有一个对内存要求很高的地方就是用 char 存的 int 类型,然后用 pack(1)的方法强制了 1 个字节对齐,CPU 这块还没有评估 |
30 xuanbg 2019-07-30 20:45:57 +08:00 多种状态复合判断的时候,位操作挺好使的。 |
31 rayhy 2019-07-30 21:16:53 +08:00 via Android 用位操作算 flag 不是蛮常用的吗? |
32 summer20100514 2019-07-30 21:42:42 +08:00 via Android 果然 v 站都是纯程序员不搞嵌入式 |
33 gamexg 2019-07-30 21:51:40 +08:00 |
34 Takamine 2019-07-30 22:16:21 +08:00 via Android 什么,你说 hashmap,hash 一致性算法用位运算?好,我知道了,那就直接取个模。:doge: |
35 kaminic 2019-07-30 22:20:09 +08:00 via Android 大小端转换 颜色值顺序转换 比如 rgba 转 abgr 用位操作简单快速 所以还是看需求吧,位操作不是洪水猛兽 |
36 ysn2233 2019-07-30 22:51:46 +08:00 >>这种还是经常用的 |
37 OhYee 2019-07-30 23:02:33 +08:00 我觉得我就是你们要喷的用位运算的。 可是用位运算真的解决了很多后期的问题,而且我封装好并且写了注释 个人认为该用的话还是有必要用的,可读性可以靠注释和封装来弥补。 |
38 AlvaIM 2019-07-30 23:08:50 +08:00 现在的年轻人怎么啦,基础的东西学不会还则罢了, 居然学不会还喷。 |
39 qfpZ2KhNsF23UGbN 2019-07-30 23:38:50 +08:00 位运算在某些地方还是蛮方便的,比如读二进制文件。可以不用位运算的地方就尽量不用,可读性不太好。 |
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 |
41 iwtbauh 2019-07-30 23:40:45 +08:00 via Android |
42 mason961125 2019-07-30 23:46:34 +08:00 @iwtbauh #41 读 I/O 电平存一个字节为啥要用位域? |
43 iwtbauh 2019-07-30 23:50:51 +08:00 via Android @jaskle #27 不对。对齐的目的是防止跨越对齐边界读。 比如你使用的指令是 32 位操作指令。你必须使内存地址为 32 位的倍数。否则可能出现 3 种情况: 1. 你的 CPU 支持非对齐访问(如 Intel 家族)。这时性能会降低。 2. 你的 CPU 不支持非对齐房屋,但编译器发现你在这么做,于是编译器将代码展开成两次读取,然后用位运算得出正确结果。性能降低。 3. 你的 CPU 不支持非对齐访问,同时因为你的写法的原因(考虑到使用 void *指针倒了一次编译器已经没法理解了),编译器不会采取额外操作,运行时,你的程序触发总线误。 而 1 字节的 char 不会使用 32 位的读取指令读取,用的是 8 位操作的指令,没有位运算,它也不会用两个 CPU 周期。性能没有损失。而且,因为能更高效的利用 CPU 高速缓存,实际上性能会更好!对于编译器,除非有必要,编译器尽可能不把 char 扩充。 |
44 iwtbauh 2019-07-30 23:59:36 +08:00 via Android |
45 iwtbauh 2019-07-31 00:28:26 +08:00 via Android 乘 /除 2 的倍数时,我习惯写成移位 如果需要操作某种二进制协议 /文件格式时,我优先使用位域,但有时也会使用位运算 如果只是为了省内存,除非是极端情况(硬件条件极其恶劣或者运算强度需要压榨出机器最后一丝性能)拒绝使用。 |
47 muzhidianzi 2019-07-31 00:48:37 +08:00 via Android @jaskle 小白求大佬展开讲讲?还一直没思考过这个 |
48 muzhidianzi 2019-07-31 00:54:09 +08:00 via Android @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 个周期)都没有任何优势。 |
50 jaskle 2019-07-31 07:54:15 +08:00 via Android 对于位操作我的想法是看计算类型以及计算量,主要考虑到可读性和开发效率。如果对位计算有兴趣可以阅读一下 bitmap 算法相关书籍 |
51 ttgo 2019-07-31 08:00:11 +08:00 偶尔用一下,炫技。。 |
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 字节对齐。 |
54 monsterxx03 2019-07-31 09:29:04 +08:00 二进制协议解析肯定要用,比如之前写 dns 协议 parser 的时候. bitmap 和相关变种算法里很常用, 比如 bloom filter, 你得先实现个 bit array 吧. |
55 momocraft 2019-07-31 09:32:15 +08:00 如果语义一致(比如可 | & 复合) 用位运算是最自然的 |
56 Chowe 2019-07-31 09:48:27 +08:00 对不起我不仅用位运算我还各种 goto 外加直接访问物理内存:doge |
57 inhzus 2019-07-31 09:49:06 +08:00 via Android Golang 没有 enum 用位运算表示类型很正常吧… |
58 Harv 2019-07-31 10:21:44 +08:00 开发时简单运算顺手就用,但一般是发布调试前统一改,统一优化。 至于你们说后期维护...一般都会把原式注释放后面的。如果不带注释,读起来是真的伤身体。 |
59 junbaor 2019-07-31 10:24:34 +08:00 代码是给人看的,顺便让机器执行一下。如果不是基础框架,并且性能瓶颈不在那一块,建议直接打死。 |
60 AlphaTr 2019-07-31 10:50:44 +08:00 不抵触,不滥用;优先保证语义的清晰;然后才考虑性能 |
61 wangyaominde 2019-07-31 11:38:11 +08:00 之前写嵌入式,还是位操作好用,如果为了维护,还是要尽量写好注释 |
62 pmispig 2019-07-31 11:52:37 +08:00 嵌入式一般都是位操作,节省内存和流量带宽 |
63 karllynn 2019-07-31 11:55:16 +08:00 单片机写过没,老铁 |
64 jaskle 2019-07-31 12:10:31 +08:00 via Android @iwtbauh emmmm,其实你可以连续定义 2 个 uchar 然后断点,用&拿出地址,看看是不是 4 字节对齐 |
65 iwtbauh 2019-07-31 12:52:13 +08:00 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 字节对其 |
67 imycc 2019-07-31 13:57:44 +08:00 via iPhone 可以写,但是你得在位运算上面留一段注释说明意图跟原理。这也适用于其他为了让程序更高效而逻辑不直观的地方。 |
68 shawndev 2019-07-31 16:13:30 +08:00 Clean Code 一书最受启发的一点:不要把抽象层级不同的代码放在一起。 比如 IO 操作和报文校验。正则表达式和 rpc 调用。 基于这个共识,位操作能用就用。 |
69 toma77 2019-07-31 16:23:14 +08:00 写权限系统的时候用位运算比较好 |
70 jaskle 2019-07-31 17:46:46 +08:00 via Android |
71 xxdd 2019-07-31 18:10:57 +08:00 项目中没必要 维护成本远远大于省下的性能成本。 |
72 Bown 2019-07-31 19:15:27 +08:00 BLE 开发必备,传输速度太慢了,不自定义二进制协议用户没法用 |
73 spadger 2019-07-31 19:58:47 +08:00 位域了解下。 |
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 判断即可。 |
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 还原真实空间大小。 这样看来还有有点用处的。 |