操作系统程序作业

文章目录

  • 1、用C语言写一个大规模矩阵遍历的程序,在不同规模的数据上运行,比较按行遍历快还是按列遍历快,解释原因。
  • 2、下列程序是否会出现死循环?为什么?在你的PC上,调用f(60)所需要的时间大概是多少?
  • 3、写一个自启动程序,输出你的学号、姓名。要求代码有详细注释。
    • 第一种方式
    • 第二种方式
  • 4、比较以下两个函数的性能
  • 5、编译原理的应用介绍

1、用C语言写一个大规模矩阵遍历的程序,在不同规模的数据上运行,比较按行遍历快还是按列遍历快,解释原因。

#include 
#include 
#include
int main()
{
	const int MAX_ROW = 2048;
	const int MAX_COL = 2048;
	int (*a)[MAX_COL]=(int(*)[MAX_COL])malloc(sizeof(int)*MAX_ROW*MAX_COL);
	clock_t start, finish;

	//先行后列
	start = clock();
	for (int i = 0; i<MAX_ROW; i++)
	for (int j = 0; j<MAX_COL; j++)
		a[i][j] = 1;
	finish = clock();
	printf("The first travel time is %lf ms\n",(double)finish-start);
	//先列后行
	start = clock();
	for (int i = 0; i<MAX_COL; i++)
	for (int j = 0; j<MAX_ROW; j++)
		a[j][i] = 1;
	finish = clock();
	printf("The second travel time is %lf ms\n",(double)finish-start);
    return 0;
}

运行结果:

2048*2048
The first travel time is 9.000000 ms
The second travel time is 53.000000 ms
10000*10000
The first travel time is 239.000000 ms
The second travel time is 543.000000 ms
100000*100000
The first travel time is 43368.000000 ms
The second travel time is 624336.000000 ms

操作系统程序作业_第1张图片

可以明显看出,按行遍历比按列遍历快。而且矩阵规模越大,按行遍历与按列遍历时间差异越显著。
理论解释:

  1. 首先,可以肯定我的电脑操作系统是按行存储数据的;
  2. CPU高速缓存:维基百科中有以下的内容:CPU高速缓存(英语:CPUCache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。
    缓存从内存中抓取一般都是整个数据块,所以它的物理内存是连续的,几乎都是同行不同列的,而如果内循环以列的方式进行遍历的话,将会使整个缓存块无法被利用,而不得不从内存中读取数据,而从内存读取速度是远远小于从缓存中读取数据的。随着数组元素越来越多,按列读取速度也会越来越慢。
  3. 分页调度:物理内存是以页的方式进行划分的,当一个二维数组很大是如int[128][1024],假设一页的内存为4096个字节,而每一行正好占据内存的一页,如果以列的形式进行遍历,就会发生128*1024次的页面调度,而如果以行遍历则只有128次页面调度,而页面调度是有时间消耗的,因而调度次数越多,遍历的时间就越长。

2、下列程序是否会出现死循环?为什么?在你的PC上,调用f(60)所需要的时间大概是多少?

int f(int x){
    int s = 0;
    while(x++>0) s+=f(x);
    return fmax(s,1);
}

程序是否会出现死循环?

调用f(60)时,入口参数x=60。从数学的角度理解while中的判断表达式“x++ >0”,会认为x在增量后永远大于0,这是一个永真式,从而做出错误结论:程序死循环。在计算机中数值是有范围的,int型数据用补码表示,占4个字节,能表示的最大正数是231-1 = 7FFF FFFFH。231的机器数是8000 0000H,其值为int型,能表示的最小负数-2147483648,因此当x = 8000 0000H 时,x > 0的值为假,程序退出while循环,因此,若不考虑栈溢出,则程序能执行结束。
f(60)在64位系统中的实际执行情况

假设在Intel x86+windows+VC+C语言环境中执行f(60)。VC中默认分配栈的大小是1MB,虽然用户可以调整栈大小,但栈的容量是有限的。按2MB的栈空间、栈大小按80字节计算:2MB÷80B≈26214,因此f(x)递归调用的次数不会超过26214-1=26213次。从图中可以看出,栈溢出时,f(x)函数体最多执行26213次。栈溢出时每个f(x)函数体只在while语句中执行,假设每个f(x)函数体执行100条指令,即使指令平均CPI为3,时钟频率为2.4GHz,f(60)的执行时间也只有26213×100×3÷2.4GHz ≈3.2 ms左右时间。
操作系统程序作业_第2张图片
对f(60)的执行做了测试,在栈大小是1MB时,递归调用11244次后栈溢出;在栈设置为2MB时,递归调用22642次后栈溢出,显然运行时间只有几毫秒。在Microsoft VisualStudio 2019环境中运行,出现如图所示结果,表明出现了栈溢出(Stack overflow)。

3、写一个自启动程序,输出你的学号、姓名。要求代码有详细注释。

第一种方式

emm,写了个开机自启动程序,其实跟代码没什么关系,代码就几行:

#include  
  int main() 
  { 
    printf("学号:2007040128 姓名:张涵"); 
   return 0; 
  } 

那么是如何实现自启动的呢?将该程序生成的exe文件直接放入开机自启动的位置就可以了
操作系统程序作业_第3张图片
首先,win+R,将运行的对话框打开,在运行对话框输入shell:startup
操作系统程序作业_第4张图片
确定之后,就会进入C:\Users\lennovo\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup,就是你的开机自启动程序位置
然后将上面的.exe文件拖入到这个目录下,就可以了

第二种方式

将程序写入注册表设置自启动的代码:

#include 
#include 
#include 

void ComputerStart(char *pathName)
{
	//找到系统的启动项
	char *szSubKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
	HKEY hKey;

	//打开注册表启动项
	int k = RegOpenKeyExA(HKEY_CURRENT_USER, szSubKey, 0, KEY_ALL_ACCESS, &hKey);
	if (k == ERROR_SUCCESS)
	{
		//添加一个子Key,并设置值,MyStart为启动项名称,自定义设置;
		RegSetValueEx(hKey, "MyStart", 0, REG_SZ, (BYTE *)pathName, strlen(pathName));
		//关闭注册表
		RegCloseKey(hKey);
		printf("设置成功\n");
	}
	else
	{
		printf("设置失败  error:%d\n", k);
	}
}

int main()
{
	char pathName[MAX_PATH];//文件名字最大260个字符  MAX_PATH  260
	GetCurrentDirectory(MAX_PATH, pathName);//设置字符集为多字节字符集  获取当前文件路径

	sprintf(pathName, "%s\\", pathName);
	strcat(pathName, "print.exe");//找到需要开机自启动的程序

	ComputerStart(pathName);


	system("pause");
	return 0;
}

可以在任务管理器中查看启动项已经添加:
操作系统程序作业_第5张图片

4、比较以下两个函数的性能

函数一依次使用两个循环,遍历两个数组并进行累加;
函数二仅使用一个循环,遍历两个数组并进行累加。

int f1(int* p, int* q, int n)
{
	int sum = 0;
	for (int i = 0; i < n; i++)
	{
		sum += p[i];
	}
	for (int i = 0; i < n; i++)
	{
		sum += q[i];
	}
	return sum;
}

int f2(int* p, int* q, int n)
{
	int sum = 0;
	for (int i = 0; i < n; i++)
	{
		sum += p[i];
		sum += q[i];
	}
	return sum;
}

运行结果:

数组大小100000000
函数一运行时间为0.415000s
函数二运行时间为0.234000s
数组大小1000000
函数一运行时间为0.004000s
函数二运行时间为0.002000s

操作系统程序作业_第6张图片

操作系统程序作业_第7张图片

如果仅从按行访问原理来看,对于连续遍历一个一维数组,程序在访问了第一个元素之后,接下来的连续多个元素都被存放在了cache中,因此接下来的访问命中率会很高的,这就大大加快了程序运行的速度,这样的话函数一的运行时间应小于函数二运行的时间。
但是,实验结果表明,函数一的运行时间是大于函数二的运行时间的。原因模块一使用了两个循环,模块二使用了一个循环,而循环涉及到大量指令的跳转,因此消耗时间较长,导致实际运行函数一的运行时间大于函数二运行的时间。

5、编译原理的应用介绍

编译原理,说得通俗易懂一些就是:让机器通过某种机制和规则,将一种由人们书写的高级程序代码,经过若干步骤,最终翻译成机器可理解执行的二进制代码。
编译原理技术的具体应用,例如:
(1)我们用户通常编写的 C/C++ 程序源代码(.C/.CPP),通过 Microsoft Visual C++ 编译器,将由人工书写的 C/C++ 语言程序源代码(.C/.CPP),最终翻译成机器可执行的二进制代码(*.EXE);
(2)人工智能领域中的自然语言处理、机器翻译技术(例如:英/汉翻译、日/汉翻译系统等)等,都需要使用到编译原理技术。

你可能感兴趣的:(操作系统,操作系统)