计算机系统实验一:环境安装

参考教材:计算机系统基础 第二版 袁春风 机械工业出版社
参考慕课:计算机系统基础(四):编程与调试实践 https://www.icourse163.org/learn/NJU-1449521162

计算机系统实验导航

实验一:环境安装 https://blog.csdn.net/weixin_46291251/article/details/122477054

实验二:数据的存储与运算 https://blog.csdn.net/weixin_46291251/article/details/122478255

实验三:程序的机器级表示 https://blog.csdn.net/weixin_46291251/article/details/122478979

实验四:二进制程序逆向工程 https://blog.csdn.net/weixin_46291251/article/details/122479554

实验五:缓冲区溢出攻击 https://blog.csdn.net/weixin_46291251/article/details/122479798

实验六:程序的链接 https://blog.csdn.net/weixin_46291251/article/details/122480049

实验源码: xxx

准备

实验内容:

1 课程实验平台环境的安装,基本实验工具的使用;
2 从高级语言的角度展示和解释位运算、浮点数运算的精度、cache对程序性能的影响。

实验目标:

1 完成课程实验平台环境的搭建与设置;掌握常用实验工具的基本使用方法;
2 掌握C语言中位操作语句的使用;了解浮点数表示精度在浮点数运算中的影响;了解cache、数据存储与访问模式对程序性能的影响,掌握编写cache友好代码的基本原则。

实验任务:

1 学习MOOC内容
https://www.icourse163.org/learn/NJU-1449521162

  • 第一周 实验与开发环境的安装和使用
    第2讲 虚拟机、Linux及其上实验环境的安装
    第3讲 基本实验工具的使用
  • 第二周 C语言编程实践
    第1讲 数据的位运算操作
    第2讲 浮点数的精度问题
    第3讲 Cache友好代码

2 在自己的电脑上安装实验环境
安装虚拟机软件:VirtualBox 6.0.8开源软件
安装Linux系统:Linux Debian 32位
熟悉软件工具:gcc,gdb,objdump

题目一:不使用中间变量,交换变量a和b的值

编写C语言程序,不使用中间变量,交换变量a和b的值,已知变量的初始值为a=2021,b=191,分析程序的反汇编代码,说明算法的基本原理。
程序代码和注释说明

1.#include <stdio.h>
2.#include <stdlib.h>
3.
4.int main()
5.{
6. int a=2021,b=191;
7. printf ("before swapping: a=%d, b=%d\n",a,b);
8. 
9. a = a^b;
10. b = b^a;//b=b^(a^b)=a
11. a = a^b;//a=(a^b)^a=b
12. printf("after swapping: a=%d, b=%d\n",a,b);
13.}

第一步:b=b(ab)=a
第二步:a=(ab)a=b
然后就完成了数值交换并且没有使用其他中间变量。

实验结果记录

在这里插入图片描述

可见实现了不使用中间变量,交换变量a和b的值

题目二:编写C语言程序,说明浮点数运算误差问题,并给出解决方案。

注:可以参考kahan累加算法的例子,MOOC内容(第二周 第2讲 浮点数的精度问题);也可以采用其他算例,分析运行效果,说明算法的基本原理。
程序代码和注释说明

1.#include<stdio.h>
2.void main()
3.{
4. float sum = 0;
5. float sum1 =0;
6. float c = 0;
7. float y, t;
8. int i;
9. for (i=0;i<4000000;i++)
10.  sum1+=0.1;
11.
12. for( i=0;i<4000000;i++)
13. {
14.  y=0.1-c;
15.  t=sum+y;
16.  c = (t-sum) -y;
17.  sum = t;
18. }
19. 
20. printf ("sum1=%f\n" , sum1) ;
21. printf ("sum =%f\n" , sum) ;
22.}

上述代码利用两种方法对0.1累加四百万次,并分别输出累加后的结果。

实验结果记录

在这里插入图片描述

第一个值为直接累加得到的,第二个值为kahan算法累加得到的。这里的c是累计产生的误差,y经过误差修正后的加数,t是经过本次累加后的和,t-sum为本次累加实际加上的加数。

通过上述运行结果可知浮点数相加会带来一定的误差。浮点数的运算中有对阶、舍入、溢出等问题,导致运算结果会出现大数吃小数、精度误差、结果异常等问题。

截断误差大的原因:

1.据浮点数的定义,浮点数的值越大,在实数轴上越靠右,相邻的两个能表示的浮点数的间距也越大,这就造成截断误差越大。
2.浮点数进行加减法时需要对阶,而且是小阶码向大阶码对齐,尾数右移,移出的比特舍弃。这样两个加数的值相差的越多,较小的数的尾数舍弃的比特也就越多,造成的误差越大。
所有这些作用在400w次加法上,也就造成了15475.21875的误差,所以一定要重视浮点数累加的精度问题。

解决方案:

代码如上:
其中c表示累积误差,初始值为0,y是下一个加数经过调整后的结果,然后用调整过后的y加上sum,对其舍入后得到10003.1。显然此时已经产生了舍入误差,如何计算该误差呢?可以用10003.1减去加数之前的和也就是10000,然后减去添加的加数也就是3.14159,这样就得到了这次加法的一个累积误差,循环往复即可得到正确值。

该算法的主要思想是:设法计算出每次累加所带来的的舍入误差,并将其添加在下一次的加数上,这样就可以获得更为准确的结果。使用前提是尽量在浮点数数值相近时进行加减计算,才能充分利用有效位数。

题目三:编写C语言程序,实现两个1024*1024的浮点数矩阵相乘,采用不同的循环顺序,比较运行效果,并分析导致差异的原因。

程序代码和注释说明
如果我们需要在某个任务中多次使用一个数据时,应尽可能地一次性使用完,这主要是利用了数据的时间局部性。在访问数组时应依次地访问元素,这利用了数据的空间局部性。因为当我们访问一个内存地址单元时会将同一块的数据,同时从内存读入Cache,这样如果我们继续访问附近的数据,那它已经位于Cache中,访问速度就会很快。

1.#include <stdio.h>
2.#include <stdlib.h>
3.#include <sys/time.h>
4.#include <time.h>
5.
6.void multMat1( int n, float *A,float *B, float *C ) {
7.    int i,j,k;
8.    /* This is ijk 1oop order. */
9.    for(i=0;i<n;i++)
10.        for(j=0;j<n;j++)
11.            for(k=0;k<n;k++)
12.                C[i+j*n] += A[i+k*n] *B[k+j*n] ;
13.}
14.
15.
16.void multMat2( int n,float *A,float *B, float *C ) {
17.    int i,j,k;
18.    /* This is ikj loop order. */
19.    for(i=0;i< n; i++ )
20.        for(k=0;k<n;k++)
21.            for(j=0;j<n;j++)
22.                C[i+j*n] += A[i+k*n] *B[k+j*n] ;
23.}
24.
25.void multMat3( int n,float *A,float *B, float *C ) {
26.    int i,j,k;
27.    /* This is jik loop order. */
28.    for(j=0;j<n;j++)
29.        for(i=0;i< n; i++ )
30.            for(k=0;k<n;k++)
31.                C[i+j*n] += A[i+k*n] *B[k+j*n] ;
32.}
33.
34.void multMat4( int n,float *A,float *B,float *C ) {
35.    int i,j,k;
36.    /* This is ikj loop order. */
37.    for(i=0;i< n; i++ )
38.        for(k=0;k<n;k++)
39.            for(j=0;j<n;j++)
40.                C[i+j*n] += A[i+k*n] *B[k+j*n] ;
41.}
42.
43.void multMat5( int n,float *A,float *B,float *C ) {
44.    int i,j,k;
45.    /* This is kij loop order. */
46.    for(k=0;k<n;k++)
47.        for(i=0;i< n; i++ )
48.            for(j=0;j<n;j++)
49.                C[i+j*n] += A[i+k*n] *B[k+j*n] ;
50.}
51.
52.void multMat6( int n,float *A,float *B,float *C ) {
53.    int i,j,k;
54.    /* This is kji loop order. */
55.    for(k=0;k<n;k++)
56.        for(j=0;j<n;j++)
57.            for(i=0;i< n; i++ )
58.                C[i+j*n] += A[i+k*n] *B[k+j*n] ;
59.}
60.
61.
62.
63.
64.int main( int argc, char **argv ) {
65.    int nmax = 1024,i,n;
66.
67.    void (*orderings[]) (int,float *,float *,float *)= { &multMat1 , &multMat2, &multMat3,&multMat4 , &multMat5 , &multMat6} ;
68.    char *names[] = {"ijk","ikj", "jik" ,"jki", "kij", "kji"};
69.    
70.    float *A = (float *)malloc( nmax*nmax * sizeof (float)) ;
71.    float *B = (float *)malloc( nmax*nmax * sizeof (float)) ;
72.    float *C = (float *)malloc( nmax*nmax * sizeof (float)) ;
73.    
74.    struct timeval start, end;
75.
76.    /* fill matrices with random numbers */
77.    for(i=0;i<nmax*nmax;i++) A[i]=drand48() *2-1;
78.    for(i=0;i<nmax*nmax;i++) B[i]=drand48() *2-1;
79.    for(i=0;i<nmax*nmax;i++) C[i]=0;
80.    
81.    
82.    for(i=0;i<6;i++)
83.    {
84.        /* multiply matrices and measure the time */
85.        gettimeofday( &start,NULL ) ;
86.        (*orderings[i]) ( nmax, A, B, C ) ;
87.        gettimeofday( &end, NULL ) ;
88.        
89.        /* convert time to Gflop/s */
90.        double seconds =( end.tv_sec - start.tv_sec)+ 1.0e-6*(end.tv_usec - start.tv_usec) ;
91.        printf( "%s:\tn = %d,  %.3f s\n", names[i] ,nmax, seconds ) ;
92.    }
93.}

实验结果记录

执行程序,结果如下:
在这里插入图片描述

可见运行用时有较大差异

不管多少维的矩阵,在计算机内部都是顺序存放的,在C语言中,二维矩阵采用的是行优先存储,也就是依次顺序地存储每一行的数据。当我们访问相邻数据时,应当尽可能地遵从空间局部性和时间局部性。

当按照ijk顺序循环时,矩阵B中第j行与矩阵A中第i列对应的元素相乘并累加,得到矩阵C中的第j行第i列元素的值,可以看到矩阵A的访问不是顺序的,而是跨越了一行的数据,因此如果矩阵过大,Cache放不下整个矩阵的数据,矩阵A的访问就会很慢。
在这里插入图片描述

与ijk类似,按照jik顺序循环时,矩阵B中第j行,与A中的第i列对应的元素相乘并累加,得到C中的第j行第i列元素的值。矩阵A的访问不是顺序的,而是跨越了一行的数据,因而它的访问速度也不快。
在这里插入图片描述

按照jki循环时,矩阵B中第j行第k列元素分别与矩阵A的第k行元素相乘,得到C中的第j行元素的部分值。此时对A和C的访问是顺序的,对Cache的利用最好,因此jki的运行速度最快。
在这里插入图片描述

上面的乘法考虑的都是空间局部性,矩阵相乘还有时间局部性问题,以ijk顺序计算为例,矩阵B中每一行依次与A中的每一列相乘并累加,得到C中的一个元素值。矩阵B的时间局部性很好,每一行读取后,与矩阵A中所有的列依次相乘,当它计算完以后,不会再去使用改行的数据,但矩阵A的时间局部性很差,A中的某一列会在不同的时间被多次读取使用。

一个常用的算法是分块矩阵相乘,即分块算法,可以部分减缓该问题。不再以行和列为单位进行计算,将矩阵分成同样大小的若干块,以块为单位进行计算,提升矩阵A的时间局部性。
在这里插入图片描述

  • 7
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cheney822

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值