相信我! Android开发 必须要了解的计算机基础知识

340 阅读6分钟

1 CPU和内存

CPU和内存 是核心中的核心。 先上图:

image.png

一个CPU会有单个、或多个核。每个核都自己的寄存器、PC、ALU、缓存等。多个核之间共享L3缓存。与内存通过总线来传输。

晶体管组成逻辑门电路,CPU是几十亿个这样的逻辑门组成。能够非常快速的实现计算。 CPU:我只管做事,你只要输入数据,然后要我怎么做我很快就告诉你结果。但是如果你想让我自动的去做事,那么就需要内存了。 写程序就是在往内存存入指令(程序)和数据。CPU的本质很简单。就是不断的取指令做运算(通过逻辑电路),一直取到指令结束为止。

CPU根据PC(下一条指令的地址)从内存拿到数据,先存到寄存器。CPU有几十个(具体多少不用纠结)不同功能的寄存器。存到寄存器后,ALU(Arithmetic&logical Unit 逻辑运算单元)才开始真正的计算,就是用来逻辑处理的。 计算完成在写入到内存中去。 内存也叫主存。

单核CPU一次只能执行一个线程,切线程时需要把数据存到缓存cache、内存保存好。然后寄存器再去加载线程2的指令和数据。

超线程:四核八线程,即一个核对应两个pc、registers。 每一个CPU的核一个ALU 对应多个PC和Registers。 两个线程同时运行的时候,运算只通过ALU在PC和registers中(ALU非常非常快)去切换,避免线程切换导致的内存切换。

CPU和内存之间通过总线来通信。总线分为很多种。有地址总线、数据总线。

2 缓存行

cache line 缓存行 大小是64 Byte。如果总线宽是32则是 16个总线单位长;如果是64那就是8个总线长。 在内存中分好的。 CPU每一次读取一个cache line,然后存到L3,在存到L2,在存到L1,然后在到寄存器进行计算。

这也就解释了,为什么连续的存储空间数组比链表可以让CPU更高效率的执行!

2.1 缓存一致性协议 MESI Cache。

  • m: modified 已修改
  • e:exclude 独自拥有
  • s:shared 共享,但都不改
  • i:invalid 无效的

这是为了解决多核CPU间L1 L2缓存间的cache line数据不同步问题。 所以volatile是用来保证缓存一致性的。

如果变量x是volitale修饰的?

CPU1修改了缓存行中的数据x,那么会立即同步到主存,也就是内存。同时通过硬件缓存锁(或者总线锁)来通知CPU2,CPU2在读取最新数据到本身的L1/L2缓存中,最终到寄存器中。 注意: 单核CPU是不存在数据不可见的问题的。

但这会引发一个问题:

如果x和y都被volitale修饰,并且在同一个cache line中。而两个CPU分别读取了这x和y,那么会出现互相通知的情况,导致CPU被不断的中断刷新缓存和寄存器。从而性能下降。

那怎么解决呢?

通过在前后各添加7个long型的数据(56位),然后加上自己本身的long。总共就是64位,那么无论怎么分cache line,该数据都不会被分到同一行。

3 指令重排序

CPU对于指令是乱序执行的。 假设有指令1和指令2,当CPU执行指令1的时候(指令1是去内存读取数据比较慢,如读取IO数据或去内存读数据),此时CPU处于读等待状态,为了不浪费性能,CPU会检测指令2 ,如果指令2跟指令1没有依赖关系,那么会执行指令2。从而出现指令2比指令1先执行了。

本质就是一系列有顺序的指令。但是其中有的指令之间没有依赖关系,是可以在读等待装填去执行的。出现了并发乱序执行。

从java层看似一个操作,实则到汇编指令的时候是分了三步走的。

java建立对象的汇编指令Object t= new Object();分三步走:

  1. 分配内存
  2. 初始化
  3. 建立关联

4 原码、补码、反码

首先要正确理解他们的作用!

计算机存储和识别的都是补码。 为什么?因为计算机只想做加法操作。不管是减法还是乘法、除法,我都通过加法规则来完成。

但是,如果是负数,那么补码就不是它表面看起来的值。

例如:有符号数:111 按照我们第一印象就是1负数,11是值,所以这个数是-3。 这样理解是错误的!

我们要知道,这是计算机的补码。真正的数值是多少计算机内部是知道的,但是我们此时看不出来!

因此,需要转成原码,然后计算成十进制。 补码111-->反码是110-->原码是001-->1-->添加负号:-1。 所以,真正的值是-1。 具体转换规则是怎么样的,接着往下看。

因此,原码和反码都只是工具,都是为了推导出补码而存在的。计算机根本不管这个!

此外,原码、补码都是针对负数才有意义!正数根本不需要!

原码:方便给人计算,查看真正的值。

反码:推导过程的中间一环而已(对原码除符号位外,取反得到反码)

补码:计算机真正需要的东西,只能识别它!存储它!(对反码+1得到补码)

4.1 原码到补码的过程

  1. +1
  2. 除了符号位,按位取反

4.1 补码到原码的过程

  1. -1
  2. 按位取反:
  3. 转换成十进制
  4. 添加负号

4.1 一个Byte表示的范围是多少

  • 无符号范围: 0~ 255
  • 有符号范围:-128~ 127

解答过程:

一个Byte是8位,也就是 0000 0000 ~ 1111 1111

无符号简单:

  • 0~(2^7+2^6+2^5++2^4+2^3+2^2+2^1+2^0)
  • => 0~255。

有符号正数,符号位为0表示正数:

我们需要将补码转成原码: 不变。

  • 范围: 0 000 0000~ 0111 1111
  • => 0~(2^6+2^5++2^4+2^3+2^2+2^1+2^0)
  • =>0~ 127。

有符号负数,符号位为1表示负数:

我们需要将补码转成原码:

范围: 1 000 0000~ 1111 1111

首先对1 000 0000进行转换:

  • -1操作:0111 1111
  • 取反: 1000 0000
  • 十进制: 2^7=128
  • 添加负号:-128

再对1111 1111进行转换:

  • -1操作:1111 1110
  • 取反: 0000 0001
  • 十进制: 1
  • 添加负号:-1

因此,负数范围为-128~ 1。

整个范围为: -128~ 127(也就是-2^7 ~ 2^7-1)

4.2 一个int表示的范围是多少

同上推到过程,范围为:-2^31 ~ 2^31 -1。