加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

为什么第二个循环比第一个循环更快?

发布时间:2020-12-16 10:34:43 所属栏目:百科 来源:网络整理
导读:我有以下代码 #include string.h#include time.h#include stdio.h#define SIZE 100000000char c[SIZE];char c2[SIZE];int main(){ int i; clock_t t = clock(); for(i = 0; i SIZE; i++) c[i] = 0; t = clock() - t; printf("%dnn",t); t = clock(); for(i
我有以下代码

#include <string.h>
#include <time.h>
#include <stdio.h>
#define SIZE 100000000

char c[SIZE];
char c2[SIZE];

int main()
{
   int i;
   clock_t t = clock();
   for(i = 0; i < SIZE; i++)
       c[i] = 0;

   t = clock() - t;
   printf("%dnn",t);

   t = clock(); 
   for(i = SIZE - 1; i >= 0; i--)
      c[i] = 0;

   t = clock() - t;
   printf("%dnn",t);
}

我已经运行了一两次,第二次打印总是显示一个较小的值…但是,如果我在其中一个循环中将更改c更改为c2,则两个打印之间的时差变得可以忽略不计……是什么原因为了这个区别?

编辑:

我已经尝试使用-O3进行编译并查看了程序集:有2次调用memset但第二次仍然打印较小的值.

解决方法

当您在C中定义一些全局数据时,它是零初始化的:

char c[SIZE];
char c2[SIZE];

在linux(unix)世界中,这意味着,c和c2都将在特殊的ELF文件部分中分配,.bss

… data segment containing statically-allocated variables represented solely by zero-valued bits initially

创建.bss段是为了不在二进制文件中存储所有零,它只是说“这个程序想拥有200MB的归零内存”.

当您加载程序时,ELF加载程序(内核为经典静态二进制文件,或ld.so动态加载程序也称为interp)将为.bss分配内存,通常类似于具有MAP_ANONYMOUS标志和READ WRITE权限/保护的mmap请求.

但是OS内核中的内存管理器不会给你所有200 MB的零内存.相反,它会将进程的虚拟内存部分标记为零初始化,并且此内存的每个页面都将指向物理内存中的特殊零页面.这个页面有4096字节的零字节,所以如果你从c或c2读取,你将获得零字节;而这种机制允许内核减少内存需求.

零页的映射是特殊的;它们被标记为(在page table中)为只读.当您第一次写入任何此类虚拟页面时,General protection fault或pagefault异常将由硬件生成(我会说,由MMU和TLB).此错误将由内核处理,在您的情况下,由minor pagefault处理程序处理.它将分配一个物理页面,用零字节填充它,并重置刚加入的虚拟页面到该物理页面的映射.然后它将重新运行故障指令.

我转换了你的代码(两个循环都移动到单独的函数):

$cat b.c
#include <string.h>
#include <time.h>
#include <stdio.h>
#define SIZE 100000000

char c[SIZE];
char c2[SIZE];

void FIRST()
{
   int i;
   for(i = 0; i < SIZE; i++)
       c[i] = 0;
}

void SECOND()
{
   int i;
   for(i = 0; i < SIZE; i++)
       c[i] = 0;
}


int main()
{
   int i;
   clock_t t = clock();
   FIRST();
   t = clock() - t;
   printf("%dnn",t);

   t = clock(); 
   SECOND();

   t = clock() - t;
   printf("%dnn",t);
}

使用gcc b.c -fno-inline -O2 -o b编译,然后在linux的perf stat或更多通用/usr/bin/time下运行以获取pagefault计数:

$perf stat ./b
139599

93283


 Performance counter stats for './b':
 ....
            24?550 page-faults               #    0,100 M/sec           


$/usr/bin/time ./b
234246

92754

Command exited with non-zero status 7
0.18user 0.15system 0:00.34elapsed 99%CPU (0avgtext+0avgdata 98136maxresident)k
0inputs+8outputs (0major+24576minor)pagefaults 0swaps

因此,我们有24,5千个小页面错误.如果x86 / x86_64上的标准页面大小为4096,则接近100兆字节.

使用perf record / perf report linux profiler,我们可以找到发生页面故障(生成)的地方:

$perf record -e page-faults ./b
...skip some spam from non-root run of perf...
213322

97841

[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.018 MB perf.data (~801 samples) ]

$perf report -n |cat
...
# Samples: 467  of event 'page-faults'
# Event count (approx.): 24583
#
# Overhead       Samples  Command      Shared Object                   Symbol
# ........  ............  .......  .................  .......................
#
    98.73%           459        b  b                  [.] FIRST              
     0.81%             1        b  libc-2.19.so       [.] __new_exitfn       
     0.35%             1        b  ld-2.19.so         [.] _dl_map_object_deps
     0.07%             1        b  ld-2.19.so         [.] brk                
     ....

所以,现在我们可以看到,只有FIRST函数会生成页面故障(在第一次写入bss页面时),而SECOND不生成任何页面故障.每个页面故障都对应于一些工作,由OS内核完成,而这项工作每页只能执行一次bss(因为bss没有取消映射并重新映射回来).

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读