• 深入理解计算机系统(Computer Systems: A Programmer's Perspective)


    学习资料:

    https://www.bilibili.com/video/BV1iW411d7hd  (字幕)

    https://www.bilibili.com/video/BV1a54y1k7YE   (无字幕)

    深入理解计算机系统书籍pdf : 链接: https://pan.baidu.com/s/1zWGfoxn0os0s9B3A2joM0Q 提取码: t4uc 

    豆瓣9.5分的书籍,这是一本"入门"级别的书,覆盖面很广,从计算机的基本组成,二进制数据表示方式,到机器级别的指令,CPU工作方式,存储结构和优化,操作系统的虚拟内存管理,程序运行方式,I/O,网络、到程序性能优化和并行程序开发等,实验部分设计的也很精彩。 本视频对应于该书第3版,是原作者Randal E.Bryant的授课内容。 课件下载: https://pan.baidu.com/s/1ZKz5kYs4c1aWdmosEOx0bw 提取码:ru0t

    Lecture01  Bits,Bytes,Integers:

    这个课程涉及的都是x86-64位机器

    位运算:

    一个应用(表示集合):

    位移操作:

    左移的时候,只是用0填充,

    右移分为 逻辑右移算术右移,其中的算术右移填充是用最高位进行填充的!

    有符号整数:

    例:

    这里假定w=4(4位),

    机器中能表示的所有情况:

    0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111  总共有16中表示方法,

    第一种方法(补码):B2T(现代计算机都是使用这种编码方式表示有符号整数)

    0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 

       0        1     2       3        4     5        6      7        -8      -7      -6     -5       -4    -3       -2     -1  

    第二种方法(反码):B2O

    0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111  

      +0        1     2       3        4     5        6      7       -7     -6     -5       -4    -3       -2     -1     -0  

    第三种方法(原码):B2S

    0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111  

      +0      1     2       3        4        5      6        7      -0      -1     -2     -3       -4    -5       -6     -7 

    参考: https://www.cnblogs.com/zach0812/p/11422697.html

    有符号数的扩展,在不改变值的情况下扩展比特:

    基本的规则是:将符号位复制到左侧来完成,

    例如:

    0110 -> 0 0110 ,它都是代表6 

    1110 -> 1 1110 , 它都是代表-2 

    截断

    加法:

    例如: w=4 ,

    1101 + 0101  = 1 0010 

    13           5             18%16 = 2 

    乘法:

    除法的右移

    求相反数

    将所有的比特反转 然后再加1  

    整数运算总结:

    为什么仍要使用无符号整数

    无符号整数容易产生的问题:

    既然无符号整数和有符号整数这么复杂,那么我们为什么还使用无符号整数,不应该去掉无符号整数吗(Python,Java就只有有符号整数)?

    计算机的字长:

    Byte Ordering :

    Lecture02  Floating Point:

    w = 1 -> 8 所能表示的小数:

    import matplotlib.pyplot as plt
    import copy
    
    
    def PowerSetsRecursive(items):
        # the power set of the empty set has one element, the empty set
        result = [[]]
        for x in items:
            result.extend([subset + [x] for subset in result])
        return result
    
    
    input_arrs = []
    temp_arr = []
    for i in range(1, 9):
        temp_arr.append(i)
        input_arrs.append(copy.deepcopy(temp_arr))
    
    w = 1
    for input_arr in input_arrs:
        subsets = PowerSetsRecursive(input_arr)
        datas = [0, ]  # 要绘制的数据
        for arr in subsets:
            if not arr:
                continue
            s = 0
            for i in arr:
                s += (1 / 2) ** i
            datas.append(s)
        # 对 datas 排序
        datas = sorted(datas)
    
        # 绘制datas
        ids = []  # 横坐标
        for i in range(len(datas)):
            ids.append(i)
    
        #print(ids)
        #print(datas)
        plt.scatter(ids,datas)
        plt.title(f"w={w}")
        plt.savefig(f"w={w}")
        #plt.show()
        w += 1
    View Code

    上述表示方法的局限:

    它只能表示   形如 :   这样的小数,

    其他的小数只能近似的表示,

    例如,1/5 的表示,

    用二进制表示,也有一些是循环的,就像10进制中的1/3 表示为0.3333333 [3]  

    具体计算过程如下:

    IEEE  浮点表示:

    IEEE 浮点标准使用如下形式来表示一个数:

    Note: Most Significant Bit,在二进制数中属于最高有效位,MSB是最高加权位,与十进制数字中最左边的一位类似。

    三种浮点数的格式:(前两种,C中的float 和 C中的double )

    01 Normalized 值:

     

    关于范围:

    容易知Exp 域 的范围是  [0,255]  0000 0000 -> 1111 1111 

    又知,Bias = 127,即  0111 1111 

    所以,实际的E 的范围是 [-127,128]     

     

     

     

    总结:

    frac域 就是 [1,2) 的小数,具体是 1.xxx...x (2进制),注:1.xxx...x的1 是隐含编码(默认就有了1,就不用再占用一个位置了)!  

    exp域  是unsigned val ,需要E 额外 加上Bias(偏移),这里可以思考个问题,为什么不用补码来表示exp域?,为了更好的做比较

    s 域 是0 或 1 

    ※关于为什么不用补码来表示exp域

    It's just another way of doing negative numbers, instead of two's complement. There's no reason for 0 to actually be represented by zeroes.

    There are 8 bits of exponent so 127 gives roughly the same positive and negative range.

     

     

    02 Denormalized 值:

    因为使用第一种normalized  values 要求M [1,2) ,所以就无法表示0。所以就有了第二种 Denormalized Values。

    它的特征是 exp域全为0 ,它的frac 中没有隐含1, 所以frac 的编码和M完全等价, 

    为什么要用 Denormalized 来表示接近0的浮点数,用范围 换精度  !

    Special 值:

    总结:

    例子:

    假设一个浮点数exp 是4个bits,frac 是3个bits 

    画图来表示:

    假设exp 3bit ,frac 2 bit 

    完整范围:

    -1 ~ 1 附近的视图放大后如下:

    IEEE Encoding 的特殊性质:

    1. 浮点0 被编码为了整数0 
      1. All bits = 0 
    2.  除了NaN之外,可以比较所有的浮点数,具体方法是通过将其视为无符号的整数
      1. Must  first compare sign bits (必须首先比较最高位)
      2. Must consider -0 = 0 (必须考虑 -0 = 0 )  

     

    舍入:

    IEEE 有四种舍入模式:

    最后一种模式是:4舍6入,5取偶   

    二进制数字的舍入:

    浮点数乘法:

    两个符号位的操作就是异或的操作,可见只有相同的时候,异或才是1 ,不一样就是0 。

    两个浮点数相加:

    浮点数乘法可以交换,但不具有结合性,

    浮点数加法可以交换,但不具有结合性,经常发生在一个很大的数字和一个很小数字的操作上,例如3.14 和 2^10 的操作,

    总结: 浮点数是不满足结合律的, 而且一般是发生在两个相差很大的数字之间的情况,所以作为程序员一定要明白数字的变化范围,重新结合或改变运算的顺序结果可能是不相同的

    现在有两种浮点数,有IEEE单精度和IEEE双精度浮点数。

    Lecture03  Machine-Level Programming 01:Basics

    History of Intel processors and architectures

    Intel 系列的处理器俗称  x86 。  这是因为它的第一个芯片被称为8086。

    然后跳过了8186,随后推出了8286,8386。

    x86有时候又被称作CISC (Complex instruction set computer)。【在80年代早期有件大事,80年代又被称作RISC 和 CISC大战的年代,RISC是精简指令集计算机】

    4核心芯片的构成:

    8核处理器

    顺便提一句的是除了  x86结构,还有ARM  ,它们是两个主要的两种设计。

    总结:

    Intel公司,AMD公司  都是使用x86结构,

    ARM 实际是 Acorn RISC Machine ,Acorn 早期是一家公司,它们在早期决定制造自己的个人电脑,它们不打算从Intel购买芯片,最后它破产了,但是它们提出了一个相当不错的指令集(Advanced RISC),非常高效简单。现在ARM是一家总部位于剑桥的公司,它出售设计而不是芯片。

    C,assembly,machine code

    Assembly Basic:Registers,operands,move

    16个寄存器,可以用来保存整数和指针。%r 代表的是64位, %e代表的是32位。 

    movq 指令

    q of movq is quad ,意思是4个字节,64位。

    注: 出于 对硬件设计者的方便,不允许从Mem 复制到 Mem,   而且Imm (立即数)也不能作为Dest。  

    这个例子展示了非常简单的内存引用,  

    关于why S is 1,2,4,8, 因为如果这是一个数组索引,必须通过数据类型的字节数来缩放索引值,如果它是一个int,就必须缩放4倍,如果是long,就缩放8倍,这也是它们存在的原因。 

    上面是  汇编代码中内存引用的基本格式,如果不使用某些字段,可以删除其中的一些字段,如下:

    Arithmetic & logical operations 

    lea 指令:

    这里是将 3*%rdi 存储到  %rax 中,然后再讲%rax 左移2位,这也是lea 和 mov 的区别,lea 是不使用内存引用!

    总之一句话,lea without memory reference, lea 常用于 整数和地址的运算~    

    这种做法比直接乘12 会好,因为乘法运算会很慢, 

    注:这里Src在前,Dest在后,

      

    一个例子:

    lea 的两种用法(其实是一种):

    01 Computing arithmetic expressions of the form x + k*y

    #include <stdio.h>
    
    long m12(long x){
        return x*12;
    }
    
    int main(){
    
        return 0;
    }
    01_arthimetic.c

    02 Computing addresses 

    #include <stdio.h>
    
    long* compute_address01(long* arr){
        long *p = &arr[0];
        return p;
    }
    
    long* compute_address02(long* arr){
        long *p = &arr[1];
        return p;
    }
    
    long* compute_address03(long* arr){
        long *p = &arr[2];
        return p;
    }
    
    int main()
    {
        long arr[] = {0,0,0,0,0};
        compute_address01(arr);
        compute_address02(arr);
        compute_address03(arr);
    
        return 0;
    }
    02_compute_address.c

    使用 gcc xxx.c -Og -S 就可以产生对应的 xxx.s 的汇编代码了。 

    Lecture04  Machine-Level Programming 02:Control

    现在有几个问题,

    寄存器(Reg) 是 内存(Mem) 的一部分? No,

    寄存器(Reg) 是缓存(Cache) 的一部分? No,

    寄存器是CPU内部用来存放数据的一些小型存储区域 。  

    本节主要是表述 如何控制机器级别指令的执行顺序,以及如何使用这些技术来实现基本的条件语句,循环和switch 语句 等。 

    Control:  Condition codes 

    有一些指令,它们唯一的作用就是设置条件码,

    这里需要提醒的是,在机器级编码中的参数顺序通常与高级语言中的顺序相反,例如:cmpq Src2,Src1, 我们在高级语言中一般是 cmp Src1,Src2。

    test 指令的唯一目的也是设置条件标志,

    movzbl 指令,是从%al 扩展到 %eax ,eax 虽然是只有32位,但是在x86的世界里,如果对低32位做了操作,那么也相当于对高32位做了操作,

    Conditional branches 

    由于条件分支的汇编代码   和   C的goto 语句  很类似,由因为汇编代码可读性较差,所以下面都是用goto 形式来表述 条件分支的汇编代码。 注:(C中不建议使用goto语句)

    为什么不能一直使用Conditional Move?

    1,进行两种情况的计算可能是一个非常非常糟糕的主意,

    2,有时候确实不能这样做,例如要保证p非空指针,

    3, 执行程序会影响其他分支的结果 ,例如第三种情况

    Loops  

    两种方式,生成While 循环的汇编代码:

     

    第三种循环时for 循环:

    Switch Statements   

    switch 语句的汇编形式,

    switch 的简单用法:

    我们知道可以用if-else 代替 switch,所以,你可能觉得switch 的汇编代码也是 if-else 类似的汇编代码,实际上不是的!

    注: ja : Jump above ,这里使用ja 而不是使用jg(jump greater),是因为如果x是负数,进行无符号的比较也会变为很大的正数。 总之,如果x 大于6,或者小于0都会直接跳到 default!   

     

    问题:

    如果x是负数,通常会加上一个偏置Bias 变为正数

    如果只有两个case ,case 0 和 case 10000 ,难道要建立 10000个项的表吗? 的确是的,所以编译器会优化成为if-else 的汇编代码形式,   

    虽然有时switch用到不多,但是有时候也是可以带来性能的提升,因为switch表是O(1) 的时间复杂度,if-else 是线性的复杂度O(n)  ,

    而且,如果case是稀疏的话,它会设置一个条件树,进行二分搜索,可以达到时间复杂度为: O(logn)的级别。 

    不管哪种都是优于if -else 的,   

    总结:

    基本上有三个条件控制,

    1,使用条件跳转到代码的不同位置

    2,是使用Condition moves (先把所有情况都计算出来,再决定跳到哪个)

    3,  是使用Jump table   

    Lecture05  Machine-Level Programming 02:Procedures 

  • 相关阅读:
    蓝牙4.0BLE cc2540 cc2541 ios OAD课程(空中固件升级)[原版的,多图]
    ASP.NET文件上传和下载
    onethink和phpwind共享
    折返(Reentrancy)VS线程安全(Thread safety)
    使用更清晰DebugLog开发和调试工具
    MySql分析算法作品索引(马上,只是说说而已B-tree)
    使用shell命令分析统计日志
    刷牙LeetCode思考
    Cocos3d-x 发布第一版
    SSH连接Linux的Server超时
  • 原文地址:https://www.cnblogs.com/zach0812/p/14146495.html
Copyright © 2020-2023  润新知