有些时候确实挺方便的, 但是不那么"直观"(对于团队中的其他人)
大家的看法是什么
1
jaskle 2019-07-30 18:41:17 +08:00 via Android
非密集计算用位操作我决定扇死他,我现在翻翻以前写的 c 代码都想扇自己,为了省内存各种可读性的降低,后期维护加功能麻烦的一批!
|
2
invoke 2019-07-30 18:42:28 +08:00 15
写的时候觉得挺牛逼的。
维护的时候觉得挺傻逼的。 |
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 2
一般封装了再使用,也还行
|
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 1
我们用的很多
主要是数据量大 ( 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 3
过早优化是万恶之源
|
21
winterfell30 2019-07-30 19:42:51 +08:00
@jaskle 求教,char 不能省空间吗
|
22
orzorzorzorz 2019-07-30 19:44:18 +08:00
最多用简单的运算,比如 ~~ 之类的
|
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 2
@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
@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 2
果然 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 1
>>这种还是经常用的
|
37
OhYee 2019-07-30 23:02:33 +08:00 1
我觉得我就是你们要喷的用位运算的。
可是用位运算真的解决了很多后期的问题,而且我封装好并且写了注释 个人认为该用的话还是有必要用的,可读性可以靠注释和封装来弥补。 |
38
AlvaIM 2019-07-30 23:08:50 +08:00 4
现在的年轻人怎么啦,基础的东西学不会还则罢了, 居然学不会还喷。
|
39
socradi 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 3
@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 1
|
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 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 字节对其 |
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 还原真实空间大小。 这样看来还有有点用处的。 |