性能指标
- 响应时间快
- 吞吐率大
- CPU 时钟周期数=指令数*每条指令的平均时钟周期数(CPI)
- 时钟周期时间(Clock Cycle) 我们程序可以识别出来的最小时间单位 比如电脑的主频是2.8GHz 可以粗浅的认为CPU在1秒内可以执行2.8GHz条简单的指令,由晶体振荡器来完成每一次时间的滴答,主频越高就意味着CPU跑的越快 但是跑的越快耗电量就越高 达到物理极限会崩溃 所以也是有上限的
- 程序CPU执行时间=指令数 * CPI * 时钟周期时间
指令
一、指令集
- 指令集 例如 手机端开发的程序没办法在电脑上运行 因为他们的CPU所支持的指令集不同,一套指令集就相当于是套CPU可以识别的语言。
- 对于存储程序型计算机 指令通常存储在存储器中
二、从代码到汇编到指令
- 添加代码
int main(){
int a = 1;
int b = 2;
a = a + b;
}
- 编译- 从代码到汇编-机器码
gcc -g -c test.c // 生成汇编代码
objdump -d -M intel -S test.o // 查看机器码
如下是源代码 汇编代码和机器码的一一对应关系
test.o: 文件格式 mach-o-x86-64
Disassembly of section .text:
0000000000000000 <_main>:
int main(){
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 31 c0 xor eax,eax
int a = 1;
6: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1
int b = 2;
d: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2
a = a + b;
14: 8b 4d fc mov ecx,DWORD PTR [rbp-0x4]
17: 03 4d f8 add ecx,DWORD PTR [rbp-0x8]
1a: 89 4d fc mov DWORD PTR [rbp-0x4],ecx
}
1d: 5d pop rbp
1e: c3 ret
三、指令分类
- 算数类指令,例如加减乘除
- 数据传输类指令,例如给变量赋值
- 逻辑类指令,例如与或非
- 条件分支类指令,if/else
- 无条件跳转指令,例如函数调用
按照R I J 分类
R 算数类和逻辑操作
I 数据传输 条件分支
J 跳转指令
寄存器
一、解释
CPU内部是由多个寄存器组成 寄存器是由一个或者多个触发器或者锁存器组成,例如一个触发器就可以组成一个一位的寄存器,寄存器是由门电路组成的,N位寄存器就可以保存N位的数据 例如64为计算机 就可以有64位寄存器
二、3中主要类型的寄存器
- PC寄存器也叫做指令地址寄存器 保存的是下一条计算机要执行的计算机指令地址
- 指令寄存器 保存当前正在执行的寄存器
- 条件码寄存器 用一个一个标记位 存放计算的结果
三、if/else 看程序的执行
- 添加如下代码
#include <time.h>
#include <stdlib.h>
int main(){
srand(time(NULL));
int r = rand()%2;
int a = 10;
if (r == 0){
a = 1;
}
else {
a = 2;
}
}
- 看汇编和机器码
test.o: 文件格式 mach-o-x86-64
Disassembly of section .text:
0000000000000000 <_main>:
#include <time.h>
#include <stdlib.h>
int main(){
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 10 sub rsp,0x10
8: 31 c0 xor eax,eax
a: 89 c7 mov edi,eax
c: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
srand(time(NULL));
13: e8 00 00 00 00 call 18 <_main+0x18>
18: 89 c7 mov edi,eax
1a: e8 00 00 00 00 call 1f <_main+0x1f>
int r = rand()%2;
1f: e8 00 00 00 00 call 24 <_main+0x24>
24: 99 cdq
25: b9 02 00 00 00 mov ecx,0x2
2a: f7 f9 idiv ecx
2c: 89 55 f8 mov DWORD PTR [rbp-0x8],edx
int a = 10;
2f: c7 45 f4 0a 00 00 00 mov DWORD PTR [rbp-0xc],0xa
if (r == 0){
36: 83 7d f8 00 cmp DWORD PTR [rbp-0x8],0x0
3a: 0f 85 0c 00 00 00 jne 4c <_main+0x4c>
a = 1;
40: c7 45 f4 01 00 00 00 mov DWORD PTR [rbp-0xc],0x1
}
47: e9 07 00 00 00 jmp 53 <_main+0x53>
else {
a = 2;
4c: c7 45 f4 02 00 00 00 mov DWORD PTR [rbp-0xc],0x2
}
}
53: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
56: 48 83 c4 10 add rsp,0x10
5a: 5d pop rbp
5b: c3
上边汇编代码 cmp是一个比较指令 jne是jump if not equal 如果不相等就执行jmp指令跳转到4c的位置 把a赋值成2
这里 jump jle jne 都是跳转到指定地址的跳转指令
四、看循环如何执行
- 增加如下代码
int main(){
int a = 0;
for (int i = 0; i < 3; i ++){
a += 1;
}
}
- 看汇编和机器码
test_for.o: 文件格式 mach-o-x86-64
Disassembly of section .text:
0000000000000000 <_main>:
int main(){
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
int a = 0;
b: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0
for (int i = 0; i < 3; i ++){
12: c7 45 f4 00 00 00 00 mov DWORD PTR [rbp-0xc],0x0
19: 83 7d f4 03 cmp DWORD PTR [rbp-0xc],0x3
1d: 0f 8d 17 00 00 00 jge 3a <_main+0x3a>
a += 1;
23: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
26: 83 c0 01 add eax,0x1
29: 89 45 f8 mov DWORD PTR [rbp-0x8],eax
for (int i = 0; i < 3; i ++){
2c: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc]
2f: 83 c0 01 add eax,0x1
32: 89 45 f4 mov DWORD PTR [rbp-0xc],eax
35: e9 df ff ff ff jmp 19 <_main+0x19>
}
}
3a: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
3d: 5d pop rbp
3e: c3 ret
解释上述代码
- mov DWORD PTR [rbp-0x8],0x0 把0给了a 也就是赋值到了0x8 地址
- mov DWORD PTR [rbp-0xc],0x0 循环第一次把 0 给到0xc
- cmp DWORD PTR [rbp-0xc],0x3 循环比较 0xc 是否和3相等 如果相等就到3a地址结束
- 如果不相等就 jmp 19 <_main+0x19> 继续执行
总结
我们平时写的代码都是高级语言 为什么说的是高级语言 因为我们不用接触汇编 更不用接触机器指令,所有的操作都让编译器帮我们做了 Xcode Studio等都可以帮助我们编译 然后生成汇编 最后生成可执行文件 到我们的磁盘中 程序执行的时候再把指令一条条的读到PC寄存器中 然后由CPU计算