你觉得用不上的位运算里,隐藏着 CPU 实现的秘密

商务办公
我们学 JS 的时候都会了解下位运算,在 React、Typescript 等源码中也频繁见到位运算的踪影,但在业务代码中从来不会这么写,它好像离我们很遥远。

[[428142]]

本文转载自微信公众号「神光的编程秘籍」,作者神说要有光zxg 。转载本文请联系神光的编程秘籍公众号。

我们学 JS 的时候都会了解下位运算,在 React、Typescript 等源码中也频繁见到位运算的踪影,但在业务代码中从来不会这么写,它好像离我们很遥远。

但是位运算真的遥远么?

其实并不是。你写的所有代码最终都会转为位运算,位运算里隐藏着 CPU 实现的秘密。

下面我们就来谈一下位运算与 CPU 的关系以及位运算在代码中的应用。

从晶体管造 CPU

晶体管

先来了解下晶体管。

下面这个是三极管,它就是一种晶体管,特性是从一端通电,另外两端的电流就可以通过,不通电,另外两端的电流就不能通过。

这特性不就是一个开关么?

它和下面这个东西差不多,只不过不需要手动开关,而是通过通电来控制开关。

开和关,就是 1 和 0。计算机世界的组成单元 1 和 0 就是这么实现的。

逻辑电路

有了 01 就可以构成逻辑电路了,也就是与门、或门、非门、异或门等构成的电路。

它们的电路符号是这样的:

与门:

或门:

非门:

异或门:

它们在 JS 中怎么表示呢?

与 &

或 |

非 ~

异或 ^

它们就是通过三极管构成的逻辑电路的基本单元,能够实现基于 0 1 的逻辑。

什么逻辑呢?

1 & 0 为 0

1 | 0 为 1

~1 为 0

0 ^ 0 为 0

与或非大家比较熟悉了,这里讲一下异或:

异或是相同为 0,不相同为 1,也就是一个 0 和一个 1 才会得出 1,否则为 0

运算器

我们了解了位运算是什么,逻辑电路是什么,那有了逻辑电路能做什么呢?

能做的可多了,CPU 不就是一个大逻辑电路么,它就是建立在位运算基础上的。

比如我们实现下 ALU 运算器:

首先实现加法:

加法在二进制里面就是异或,不信我们来试一下:

1 和 0, 0 和 1 想加是 1 ,而 0 和 0,1 和 1 相加都为 0,这不就是异或么。

当然,还要处理进位,进位可以通过与运算得到,比如上面那四个,只有 1 和 1 相与才为 1,否则都为0,这就算出了进位。

能够按位相加和进位,就能实现加法器。

  1. function add(a, b) { 
  2.     let sum = a; 
  3.     let carry = b; 
  4.  
  5.     let temp
  6.     while(carry != 0) { 
  7.         temp = sum
  8.         sum = temp ^ carry; 
  9.         carry = (temp & carry) << 1; 
  10.     } 
  11.  
  12.     return sum

测试一下加法器:

注意,上面的与和异或不都有逻辑电路么,那用电路实现上面这段代码不就是硬件实现的加法器么?

有了加法,很容易就可以得到减法器:

  1. function subtract(a, b) { 
  2.     var substrahend= add(~b, 1) 
  3.     var sub= add(a, substrahend) 
  4.  
  5.     return sub 

把被减数变为相反数,再和被减数相加不就是减法么?

至于这里为什么是取反加一,就涉及到原码反码补码了,

因为 1 对应 -1,而 0 呢?并没有 -0,所以就少了一位。所以要加一才能对上,也就是补码的“补”的意思,补上没有 -0 导致的缺少的那个编码。

实现了加法器、减法器之后,乘法和除法也就有了,因为乘法不就是多个加么?除法不就是多个减么?

就这样,我们从位运算实现了加减乘除。

对应到硬件上呢?就是我们通过三极管实现了逻辑电路,然后又用逻辑电路实现了加减乘除。

CPU

上面那个东西在 CPU 里叫做 ALU,算术逻辑单元,可以做逻辑运算、算术运算。

而且基于三极管还可以做到存储 0 1 等目的,构成寄存器。

有了这些东西我们就可以实现一个 CPU。

神不神奇,通过晶体管和位运算,我们就能把 CPU 造出来,虽然也就需要数亿个晶体管吧。

当然,我们只了解这些意义不大,还要了解位运算的应用。

位运算的应用

文件系统

看过我前面一篇文件系统实现原理文章的小伙伴会知道,硬盘会划分为数据块来使用,一个文件就是由多个数据块构成的:

文件会通过一个叫 inode 的结构来记录用到了哪几个数据块:

那我想存文件的时候,怎么知道哪些块可用呢?

一个块只有空闲和非空闲两种状态,一个位 0 和 1 就可以保存,所以会用位图这种结构来记录。

比如当我存储图片占用了 2 和 4 号块的时候,就会把位图的对应位置置为 1,否则为 0。那么当我需要存储文件的时候,从位图中查一下就知道哪些数据块可用了。

通过位图记录状态,通过位运算来判断状态。占用空间小,运算效率高。

操作系统级别都是这么干的,很多追求性能的库也都这么干。

React 和 Typescript

在 React 和 Typescript 等源码中,经常会见到一个叫 flags 的东西,flags 就和我上面说的位图差不多,通过一个位标识一个状态。

比如下面这段 React 的源码:

这就是判断了 这个 fiberNode 是否有 Placement 或者 Hydrating 的状态,如果有就 xxx。

再来看 Typescript 的源码中的这段代码:

~ 是取相反状态,再 & 就是判断是否没有这个状态,然后通过 | 设置到 flags 里面,意思就是这个 flags 加上没有 xx 状态的标识。

业务代码

操作系统、优秀的库中都用到了位运算,因为它们性能高,直接用电路算,存储空间也小,那是不是我们代码中可以用呢?

可以是可以,但是业务代码追求的更多是可维护性,如果你写出上面那种 typescript 的源码那样的位运算,后面接手的人不想打死你才怪。

总结

CPU 通过晶体管实现了电路的开关,也就是 0 和 1,然后组成了与或非异或门,进一步构成逻辑电路,逻辑电路可以实现加减乘除,构成 ALU,加上寄存器等部件就构成了 CPU。

所以位运算是直接用电路算,效率最高,其他的运算最终也会转为位运算。

操作系统文件系统的设计就用到了位图和位运算,React 和 Typescript 源码中也大量用到 flags。但这不意味着业务代码就可以用,因为业务代码还是可维护性更重要一些。

 

位运算里面隐藏着 CPU 实现的秘密,并不只是一个炫技的手段那么简单。

 

责任编辑:武晓燕 来源: 神光的编程秘籍
相关推荐

2017-12-26 15:38:18

iMac隐藏秘密

2013-11-04 17:02:40

Windows 8.1隐藏功能

2022-09-22 13:56:56

线程Java

2019-11-12 13:43:05

云计算IaaS技术

2019-02-18 08:17:40

工业物联网IIOT物联网

2021-03-18 11:09:13

比特币加密货币区块链

2021-10-10 15:01:09

Go 源码Github

2015-12-09 16:21:41

2015-10-26 09:32:16

春雨医生微信传播链

2019-09-25 17:22:31

戴尔

2021-04-27 11:15:51

Java 8ConcurrentHBUG

2015-07-30 06:19:48

Linux云计算原生计算基金会

2024-08-08 19:13:19

2015-10-27 09:09:44

2021-11-09 23:14:01

手机飞行模式功能

2022-02-07 15:24:53

iOS苹果系统

2015-11-19 10:59:00

2021-08-04 10:14:15

云计算多云云架构

2021-05-29 10:39:21

Android 12翻译谷歌
点赞
收藏

51CTO技术栈公众号