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

后端 (65) 2023-03-27 21:06

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」,希望能够帮助你!!!。
你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第1张

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

但是位运算真的遥远么?

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

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

从晶体管造 CPU

晶体管

先来了解下晶体管。

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

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第2张

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

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

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第3张

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

逻辑电路

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

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

与门:

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第4张

或门:

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第5张

非门:

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第6张

异或门:

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第7张

它们在 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,这就算出了进位。

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

function add(a, b) {
    let sum = a;
    let carry = b;

    let temp;
    while(carry != 0) {
        temp = sum;
        sum = temp ^ carry;
        carry = (temp & carry) << 1;
    }

    return sum;
}

测试一下加法器:

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第8张

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

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

function subtract(a, b) {
    var substrahend= add(~b, 1)
    var sub= add(a, substrahend)

    return sub
}

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

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

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

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

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

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

CPU

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

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

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

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

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

位运算的应用

文件系统

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

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第9张

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

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第10张

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

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

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第11张

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

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

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

React 和 Typescript

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

比如下面这段 React 的源码:

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第12张

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

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

你觉得用不上的位运算里,隐藏着 CPU 实现的秘密「建议收藏」_https://bianchenghao6.com/blog_后端_第13张

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

业务代码

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

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

总结

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

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

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

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

发表回复