readahead是Linux系统API,是提示(hint)系统将指定文件的某区域预读进页缓存, 便于接下来对该区域进行读取时,不会因缺页(page fault)而阻塞。
函数原型如下:
#include <fcntl.h>
ssize_t readahead(int fd, off64_t offset, size_t count);
readahead有以下特点: - readahead是阻塞的 - 因为是按页来读的,offset和offset+count会被合理地按页取整 - read系函数本身就有前向预读,所以只是前向使用readahead是没有多大意义的 - 不管使不使用readahead,缺页中断数都不会少,但是在复杂计算时,没有缺页打断计算,CPU流水会被充分利用
Linux系统会将用过的文件缓存于内存,虚拟文件/proc/sys/vm/drop_caches的值默认为0, 不同值代表的不同意义如下:
使用如下命令,可以强制系统进行缓存的释放:
#echo 3 > /proc/sys/vm/drop_caches
进程因IO等待很可能会被挂起,优先执行其他非IO等待进程。Linux上缺页有如下几个概念:
majflt意味着读取落到磁盘上了,有昂贵的耗时。
查询进程的缺页信息,可使用如下命令:
cat /proc/PID/stat
ubuntu上有更好用的命令,可以优雅得显示出各数字表示的意思:
prtstat -r pid
prtstat pid
先清一次缓存,然后编译如下代码,代码中做了一些无聊的计算,编译使用-O3
优化,执行程序。其中task只是用来计时的。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "task.h"
using namespace std;
int main()
{
int fd = open("~/hadoop-1.0.4.tar.gz", O_RDONLY);
int tmp = 0;
int sum = 0;
Task task;
task.set_task_us("begin");
//readahead(fd, 0, 40*1024*1024);
for (int i = 0; i < 1000000000; ++i) {
sum += i;
sum /= 2;
}
task.set_task_us("begin2");
for (int i = 0; i < 10000000; ++i) {
read(fd, &tmp, sizeof(tmp));
sum ^= tmp;
}
task.set_task_us("end");
cout << task.get_task(0) << endl;
cout << task.get_task(1) << endl;
cout << sum << endl;
close(fd);
char c;
while (cin >> c) break;
return 0;
}
程序输出为:
begin:651126us
begin2:2491863us
-1911897994
使用prtstat命令查看,可知有6次主缺页:
pid: 11205 comm: run
state: S ppid: 10743
pgrp: 11205 session: 10743
tty_nr: 34817 tpgid: 11205
flags: 402000 minflt: 367
cminflt: 0 majflt: 6
cmajflt: 0 utime: 81
stime: 232 cutime: 0
cstime: 0 priority: 20
nice: 0 num_threads: 1
itrealvalue: 0 starttime: 32043591
vsize: 12738560 rss: 263
rsslim: 18446744073709551615 startcode: 4194304
endcode: 4199478 startstack: 140735892533120
kstkesp: 7FFFA0E1AC78 kstkeip: 7F1E71B738A0
wchan: 18446744071582771801 nswap: 0
cnswap: 18446744071582771801 exit_signal: 17
processor: 0 rt_priority: 0
policy: 0 delayaccr_blkio_ticks: 0
guest_time: 0 cguest_time: 0
打开代码中的注释,清缓存,编译执行,会发现中断数一样,耗时也差不多。
将上面的代码改成如下,使用mmap来访问文件,而不是read系函数:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "task.h"
using namespace std;
int main()
{
int fd = open("~/hadoop-1.0.4.tar.gz", O_RDONLY);
int *pmap = (int*)mmap(NULL, 40*1024*1024, PROT_READ, MAP_SHARED, fd, 0);
int tmp = 0;
int sum = 0;
Task task;
task.set_task_us("begin");
//readahead(fd, 0, 40*1024*1024);
for (int i = 0; i < 1000000000; ++i) {
sum += i;
sum /= 2;
}
task.set_task_us("begin2");
for (int i = 0; i < 10000000; ++i) {
sum ^= pmap[i];
}
task.set_task_us("end");
cout << task.get_task(0) << endl;
cout << task.get_task(1) << endl;
cout << sum << endl;
close(fd);
char c;
while (cin >> c) break;
return 0;
}
上面代码输出为:
begin:659359us
begin2:270923us
-1911897994
如果解开代码中readahead注释,再编译执行,输出为:
begin:813914us
begin2:13645us
-1911897994
两次执行,缺页中断数一样,打开readahead之后,尽管第一步耗时增加,但是第二步中的计算耗时锐减,前者耗时总和为900多ms, 而后者耗时总和为800多ms,使用readahead快了约100ms。