#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
可以明显看出,按行遍历比按列遍历快。而且矩阵规模越大,按行遍历与按列遍历时间差异越显著。
理论解释:
CPUCache
,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。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左右时间。
对f(60)的执行做了测试,在栈大小是1MB时,递归调用11244次后栈溢出;在栈设置为2MB时,递归调用22642次后栈溢出,显然运行时间只有几毫秒。在Microsoft VisualStudio 2019环境中运行,出现如图所示结果,表明出现了栈溢出(Stack overflow)。
emm,写了个开机自启动程序,其实跟代码没什么关系,代码就几行:
#include
int main()
{
printf("学号:2007040128 姓名:张涵");
return 0;
}
那么是如何实现自启动的呢?将该程序生成的exe文件直接放入开机自启动的位置就可以了
首先,win+R,将运行的对话框打开,在运行对话框输入shell:startup
确定之后,就会进入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;
}
函数一依次使用两个循环,遍历两个数组并进行累加;
函数二仅使用一个循环,遍历两个数组并进行累加。
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
如果仅从按行访问原理来看,对于连续遍历一个一维数组,程序在访问了第一个元素之后,接下来的连续多个元素都被存放在了cache中,因此接下来的访问命中率会很高的,这就大大加快了程序运行的速度,这样的话函数一的运行时间应小于函数二运行的时间。
但是,实验结果表明,函数一的运行时间是大于函数二的运行时间的。原因模块一使用了两个循环,模块二使用了一个循环,而循环涉及到大量指令的跳转,因此消耗时间较长,导致实际运行函数一的运行时间大于函数二运行的时间。
编译原理,说得通俗易懂一些就是:让机器通过某种机制和规则,将一种由人们书写的高级程序代码,经过若干步骤,最终翻译成机器可理解执行的二进制代码。
编译原理技术的具体应用,例如:
(1)我们用户通常编写的 C/C++ 程序源代码(.C/.CPP),通过 Microsoft Visual C++ 编译器,将由人工书写的 C/C++ 语言程序源代码(.C/.CPP),最终翻译成机器可执行的二进制代码(*.EXE);
(2)人工智能领域中的自然语言处理、机器翻译技术(例如:英/汉翻译、日/汉翻译系统等)等,都需要使用到编译原理技术。