计算机核心概念

补码

我们先来看一个有趣的例子:

溢出128

在这里我们定义了一个signed char类型的数据,将其赋值为128,然后系统给我们一个警告,意思就是说它隐式地将数值给转换成了-128,这究竟是为什么呢?

其实在计算机中数值都是以补码的形式存储的:

正数的补码是其自身,负数的补码是其对应的正数的反码加1。

我们先看一下-42在计算机中是如何表示的:
它是先将其对象的正数42,0010 1010然后取反1101 0101,然后再加1,得到1101 0110。

我们知道signed char是占八位的。其存储范围是:0000 0000到1111 1111。那么128的补码还是128,其二进制表示为:1000 0000。但是这里有个问题,因为其类型是有符号的,所以最高位是符号位,也就是说这个数不能用来表示128了,那么它表示多少呢?我们就要看看它是谁的补码就是好了。我们先将1000 0000取反,然后的到0111 1111,加一得到1000 0000,再乘以-1,得到-128。如果不太明白,我我们可以将128变大,变成129:
溢出129

129为整数,其补码是其自身,1000 0001,又因为其最高位是符号位,所以其表示的是一个负数,它表示多少呢?先将其取反0111 1110,然后+1得到0111 1111,这时127,最后乘以-1得到-127。

switch语句为什么比if else快?

因为编译器在编译switch语句的时候会检查每个case常量,并且会创建出一个”跳转表”,这个表用来在表达式的基础上选择执行路径,其时间复杂度是O(1),而if else的时间复杂度是O(n),所以switch case要比if else快很多。

寄存器是什么?

寄存器是一种存储信息的硬件,它和内存一样可以用来存储,但是和内存不同的是,它的速度极其快(比RAM的主存要快得多,它还有一些辅助功能,它常常被用做软件和外设之间通信的桥梁,起到缓冲的作用,同时CPU中寄存器速度非常快,这就让经常需要用到的数据放到寄存器中,而不需要每次都从主存中去读取,从而提高了程序的执行速度。软件将信息写入寄存器,然后外设将信息从寄存器中读出去。如果外设有信息需要软件进行处理,那么它也需要先将信息放到寄存器中,然后有软件从中读取。寄存器的主要功能包括:

  • 某些功能的配置和初始化,特别是在初始化阶段
  • 缓存存储
  • 不同种类的输入输出
  • 状态报告,比如某个硬件的状态发生了变化

常见的寄存器包括:MAR(Memory Address Register),这个寄存器主要用来存放下一条即将执行的指令,CPU根据MAR中指令的地址取出指令,然后放到MDR(Memory Data Register中,然后CIR(Current Instruction Regitster)将指令从MDR中拷贝一份。MBR(Memory Buffer Register),用来存放要放入存储器的数据和从存储器中读出的数据。相关资料可以参考YouTube

CPU为什么需要三级缓存?

CPU和内存的速度不匹配是计算机领域的一个重要问题,如果内存中采用和CPU中相同型号的寄存器,那么其代价又会太高,并且根据局部性原理,这种需求是没有必要的。存储器的价格,速度,容量是人们考虑的主要因素,但是它们之间也存在这制约因素:速度越快,价格就越高;容量越大,价格越高;容量越大,速度越低。根据局部性原理(Principle of Locality)我们的程序中常用的数据又基本都是固定的。所以就在CPU和内存之间采用了三级缓存(它内部存储的)来提高获取数据的速度,如果数据能在L1,L2,L3中找到的话就直接从中取,如果没有再从内存取,如果内存没有,再从外存取,这样就会提高获取数据的速度。这里要注意每个处理器都有其自己的缓存区,最后他们共享一块内存,这就会造成数据不一致的严重问题。

上下文切换

在多任务执行的时候,上下文切换是指从一个线程或者进程切换到另外一个线程或者进程,这里的上下文是指:CPU寄存器和程序计数器中的内容。它的具体内容包含以下三个步骤:

  1. 挂起当前进程,将当前的CPU状态保存至内存
  2. 从内存中获取接下来将要执行进程的上下文,并且将其存入CPU寄存器中
  3. 返回程序计数器指定的位置(例如进程被终止的点)并且继续执行进程

这里的切换包含进程切换和线程切换两种情况,它们之间的相同点和不同点为:

一、进程切换

虚拟内存空间不能保持相同;调用操作系统内核;寄存器内容的切入切出和内核操作的转移是最消耗性能的;

二、线程切换

虚拟内存空间保持相同;调用操作系统内核;寄存器内容的切入切出和内核操作的转移是最消耗性能的;

这其中还有一个隐含的性能消耗点是:处理器缓存系统的失效。在虚拟内存空间切换的过程中会导致处理器的Translation Lookaside Buffer (TLB) 失效,这会造成很大的内存损耗。造成上下文切换的原因有:进程自动让出其时间片,或者调度器给它分配的时间片到了,或者硬件发生了中断操作,加锁,或者其它的软件操作也会造成上下文切换。上下文切换很消耗大量的CPU时间造成较大的性能损耗。

什么是线程?

线程是寄存器的一组状态,这组状态用来规定程序的执行顺序,执行地址,以及下条指令的地址。

什么是指令重排?

指令重排就是在执行程序的过程中,在确保不影响程序输出结果的情况下,为了提升性能,编译器和处理器对指令做的重排序:

ReSortInstruction

指令重排可以带来性能的提升,它在单线程的情况下是不会出问题的,这一点是由程序的顺序规则来保证的,如果是在多线程就会出现各种意想不到的问题。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

static int x = 0, y = 0;
static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException{

Thread one = new Thread(new Runnable() {
@Override
public void run() {
a = 1; // A1
x = b; // A2
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
b = 2; //B1
y = a; //B2
}
});
one.start();
two.start();
one.join();
two.join();
System.out.println("x =" + x + ";" + "y =" + y);
}

这段代码不仅可以输出:

1
2
3
x = 0 ; y = 1
x = 2 ; y = 0
x = 2 ; y = 1

但是在A1和A2,B1和B2重排序之后还可以输出:x = 0 ; y = 0的情况。

参考资料:

  1. 线程上下文切换和进程上下文

  2. 上下文切换的定义

  3. 线程的定义