Linux上readahead的使用

关于readahead函数

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缓存

Linux系统会将用过的文件缓存于内存,虚拟文件/proc/sys/vm/drop_caches的值默认为0, 不同值代表的不同意义如下:

  • 0 – 不释放
  • 1 – 释放页缓存
  • 2 – 释放dentries和inodes
  • 3 – 释放所有缓存

使用如下命令,可以强制系统进行缓存的释放:

#echo 3 > /proc/sys/vm/drop_caches

查看进程缺页数

进程因IO等待很可能会被挂起,优先执行其他非IO等待进程。Linux上缺页有如下几个概念:

  • minflt 该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数
  • cminflt 累计的该任务的所有的waited-for进程曾经发生的次缺页的次数目
  • majflt 该任务需要从硬盘拷数据而发生的缺页(主缺页)的次数
  • cmajflt 累计的该任务的所有的waited-for进程曾经发生的主缺页的次数目

majflt意味着读取落到磁盘上了,有昂贵的耗时。

查询进程的缺页信息,可使用如下命令:

cat /proc/PID/stat

ubuntu上有更好用的命令,可以优雅得显示出各数字表示的意思:

prtstat -r pid
prtstat pid

readahead使用

先清一次缓存,然后编译如下代码,代码中做了一些无聊的计算,编译使用-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。

发表于 2014年08月27日 11:41   评论:0   阅读:5815  



回到顶部

首页 | 关于我 | 关于本站 | 站内留言 | rss
python logo   django logo   tornado logo