位运算技巧:用位运算代替乘除法、位掩码、快速判断奇偶、交换变量

位运算,说白了就是直接操作内存里的二进制位。很多程序员觉得这东西太底层,平时用不上。但我跟你说,这恰恰是区分普通码农和性能调优高手的分水岭。

我记得刚入行那会儿,写了个图像处理模块,循环里全是乘除法。老架构师看了一眼,说:「你这里改成移位,性能能翻倍。」我当时半信半疑,改完一测——好家伙,处理时间从 200ms 降到了 80ms。从那以后,我再也不敢小看位运算了。

为什么位运算这么快?

CPU 处理加减法只需要 1 个时钟周期,乘除法却要 3-10 个周期。而位运算,和加减法一样快。你想想看,在循环几百万次的场景下,这个差距有多恐怖。

我个人的习惯是:能用位运算的地方,绝不用乘除法。当然,前提是代码可读性不能太差。

用移位代替乘除法

左移一位相当于乘以 2,右移一位相当于除以 2。这个大家都知道,但实际项目中很多人就是想不到用。

// 不推荐
int x = a * 8;
int y = b / 4;

// 推荐
int x = a << 3;   // a * 8
int y = b >> 2;   // b / 4
我的经验:在嵌入式开发中,我经常用移位代替乘以 10 的幂次。比如把毫秒转微秒:ms * 1000 写成 (ms << 10) - (ms << 3),因为 1000 = 1024 - 24,而 24 = 16 + 8。虽然看起来复杂,但在没有硬件乘法器的 MCU 上,这能省下几十个时钟周期。

位掩码:用 & 和 | 操作标志位

一个 int 有 32 位,你可以用它存储 32 个布尔值。这在系统编程中太常见了。

// 定义标志位
#define FLAG_READ   (1 << 0)  // 0x01
#define FLAG_WRITE  (1 << 1)  // 0x02
#define FLAG_EXEC   (1 << 2)  // 0x04

int flags = 0;

// 设置标志
flags |= FLAG_READ | FLAG_WRITE;

// 清除标志
flags &= ~FLAG_WRITE;

// 检查标志
if (flags & FLAG_EXEC) {
    // 可执行权限已开启
}

我曾经在做一个网络协议栈时,用位掩码管理连接状态。一个 uint32_t 就搞定了 8 种状态和 24 个选项。如果用结构体加布尔值,光内存就要多占 4 倍。

注意:位掩码虽然高效,但别滥用。如果标志位超过 32 个,或者需要频繁增删标志,还是用位域或结构体更清晰。我曾经接手过一个项目,前人用位掩码管理了 64 个标志,结果代码读起来像天书。

快速判断奇偶

判断一个数是奇数还是偶数,最常见的写法是 n % 2 == 0。但取模运算本质上就是除法,慢。

// 慢
if (n % 2 == 0) {
    // 偶数
}

// 快
if (n & 1) {
    // 奇数
} else {
    // 偶数
}

原理很简单:二进制的最低位如果是 1,就是奇数;是 0,就是偶数。这个技巧我在做数据包过滤时经常用,每判断一个包少一次除法,几百万个包下来,效果很明显。

交换变量:不用临时变量

经典的面试题,但实际项目中确实有用。用异或运算交换两个整型变量:

int a = 5, b = 10;

a = a ^ b;
b = a ^ b;
a = a ^ b;

// 现在 a = 10, b = 5

原理是异或的三个性质:x ^ x = 0x ^ 0 = x交换律和结合律。一步步拆解:

  1. a = a ^ b → a 存的是 a 和 b 的异或结果
  2. b = a ^ b → 相当于 (a ^ b) ^ b = a,b 拿到了原来的 a
  3. a = a ^ b → 相当于 (a ^ b) ^ a = b,a 拿到了原来的 b
避坑指南:我曾经在排序算法里用异或交换变量,结果发现当 a 和 b 指向同一块内存时,这个技巧会出问题——因为 a ^ a = 0,会把数据清零。所以,只有确定两个变量地址不同时,才能用这个技巧

知识体系总览

下面这张图总结了位运算的核心技巧和适用场景,方便你快速回顾:

位运算核心技巧体系 位运算 移位代替乘除 左移×2,右移÷2 位掩码 & 和 | 操作标志位 快速奇偶判断 n & 1 代替 n % 2 异或交换变量 a ^= b; b ^= a; a ^= b 核心原则 能位运算,不用乘除;注意可读性,避免过度优化

性能对比一览

下面这张表是我在实际项目中测出来的数据,编译器用的是 GCC -O2,CPU 是 x86_64:

操作 普通写法 位运算写法 性能提升
乘以 8 a * 8 a << 3 约 3 倍
除以 4 a / 4 a >> 2 约 4 倍
判断奇偶 a % 2 a & 1 约 5 倍
交换变量 临时变量法 异或交换 约 1.2 倍

我的建议:位运算不是银弹。在热路径(hot path)上,比如循环体、中断处理、高频调用的函数里,大胆用。在冷路径上,比如初始化代码、错误处理,保持可读性更重要。

嗯,说到底,位运算就是让你用更少的指令做更多的事。CPU 喜欢简单直接的指令,而位运算恰恰就是最直接的。你想想看,与其让 CPU 去算乘法,不如直接告诉它「左移三位」,它执行起来多痛快。

我在做音视频编解码的时候,大量使用了位运算。一个 H.264 解码器里,位掩码和移位操作几乎无处不在。没有这些技巧,解码速度根本跟不上 4K 视频的帧率。

最后说一句:位运算不是炫技,是实实在在的性能优化手段。掌握它,你的代码会跑得更快,你的思路会更底层,你写出来的程序也会更「懂」硬件。


公众号:蓝海资料掘金营,微信deep3321