我有一个调用两个用户定义函数的main函数。这两个函数以不同的方式执行相同的任务(在这个选择因子为50%的简单选择中)(在一个函数中使用if else而在另一个函数中使用if else)。我测量两个函数的执行时间。
int main()
{
clock_t t;
period=clock();
func1();
period=clock()-period;
print period
period=clock();
func2();
period=clock()-period;
print period
}
void func1()
{
int A[100000],B[100000],in=0;
for (i=0;i<100000;i++)
{
A[i]=i;
}
for (i=0;i<100000;i++)
{
if(A[i]==3)
B[in++]=i;
}
}
Func2与此类似,只不过我用非分支语句替换if。
当我执行程序时,首先调用哪个函数需要更多时间。在上面的例子中,func1需要更多时间。如果我首先调用func2然后调用func1,那么func2需要更多时间。我真的不明白这背后的逻辑。
任何人都可以解释。
答案 0 :(得分:0)
如果Func2
与Func1
相同,除了第二个循环中的分支,我的猜测是,对于第一个被调用的函数,操作系统必须为堆栈提交页面,而第二个函数可以只使用已提交的页面。
操作系统倾向于仅按需提交内存,以便更多程序可以适应物理内存。对于新线程,Windows提交(实际上用物理内存页面备份虚拟内存地址)可执行标头中指定的堆栈数量。然后,只有当进程触及页面时,它才会提交更多页面的堆栈。它会继续执行此操作,直到达到指定的保留大小,之后它将生成堆栈溢出异常。 Microsoft link.exe
使用的默认值是为每个堆栈保留1MB的虚拟地址空间,并提交单页(4kB)。
当进程触摸页面时,处理器会尝试在Translation Look-aside Buffer中查找虚拟地址。它将无法找到 - 一个TLB'未命中' - 因此它将尝试在进程的页表中查找它。页表将包含无效的条目,因此它将引发页面错误异常。
操作系统的页面错误处理程序查看页面表条目并确定它属于一个线程堆栈,因此它找到一个空的内存页面,修改其页面框架数据库以标记此页面现在属于此进程,修改页面表以指向新页面,然后解除该异常。处理器使用出现故障的指令重新开始执行。
处理器异常处理慢。一堆上下文信息被压入堆栈,因此处理器知道返回的位置,管道停止,TLB可能不再具有OS代码的位置,指令和数据缓存可能没有OS代码或其中的数据。
假设编译器上int
为32位,则第一个数组中有400,000个字节,第二个数组中有400,000个字节。第一个循环将生成(可能)98页错误以创建第一个数组,然后第二个循环可能会生成另一个错误以将B[0]
设置为3。
它还取决于您的编译器,操作系统和编译器选项。如果像这样在堆栈上声明大型数组,一些编译器将在函数开头生成一个“堆栈探测器”,以确保正确提交堆栈。 Windows要求进程以正确的顺序逐页提交其堆栈,因此Microsoft的编译器会在函数开头插入对_chkstk
函数的调用。这故意依次从每个堆栈页面读取,以便操作系统提交页面。这在KB 100775中有记录。
Linux reportedly just commits stack pages on-demand,并不要求程序以正确的顺序提交页面。