MMAP和madvise的配套使用



https://blog.csdn.net/bingqingsuimeng/article/details/8133529

本人做流媒体开发的,工作中需要将mpg文件转化成一定格式发送到网络,由于文件太大,程序用mmap()来实现文件的读取,但在实测过程中发现一个大问题,如下: 

服务器配置: 
CPU:两颗至强3.0 
内存:2G 
磁盘:3个SATA硬盘组成磁盘阵列 

测试时同时运行30个播放程序读取30个不同的mpg文件,程序起初运行画面播放非常流畅,几分钟过后,内存剩下15MB左右时,mmap()就开始不停 的进行页面置换,将新的数据读入内存,老的数据置换出去,这时的磁盘利用率不到1%,但CPU耗在iowait上的时间却有90%多 

各位大侠我该怎么办,如果不用内存映射还有没有其他的办法处理大文件???


开个4G的swap分区挂上去试试看 
或者,把文件分段mmap(),如100M的文件做10次mmap(),并且要求在播放1段完前做好下段准备工作

你挂mmap不释放怎么行. 
你mmap一个大文件, 要在这个大文件播放完后才能释放. 
要把大文件分小

你可以分段映射试试看,比如一次映射2M,并跟踪程序在这个映射内的使用情况,如果这此映射的数据快用完时,就提前映射下一段.在上一段用完后,就释放掉其映射.

今天又做了一下测试,先映射整个文件,在使用过程中一段一段释放,还是阻塞在IOWait,另外挂载4G交换分区的方法也试了,效果更差 
      在国外论坛上看了些IOWait的东东,有很多都说红帽企业版+Xeons处理器+磁盘阵列容易发生IOWait,和我现在的配置一模一样,用单独的大文件拷贝就能测得出来,明天下个新内核编译一下试试看

问题已解决,好高兴^_^ 

问题原因: 
    调用mmap()时内核只是建立了逻辑地址到物理地址的映射表,并没有映射任何数据到内存。 
    在你要访问数据时内核会检查数据所在分页是否在内存,如果不在,则发出一次缺页中断,linux默认分页为4K,可以想象读一个将近2G的电影文件要发生多少次中断,I can't bear it!!! 

解决办法: 
    将madvise()和mmap()搭配起来使用,在使用数据前告诉内核这一段数据我要用,将其一次读入内存,现在程序可以并发150个数据流了,每秒最高可读70MB数据 
  

感谢大家的支持,另外还要特别感谢老兵哥在“timer frequency”问题上的支持,那个问题还有待研究,周末了,祝大家愉快。

++++++++++++++++++++++++++++++++++++++++++++++++++++++

 今天在封装一个"内存映射文件"读取的类。编写WINDOWS版本的时候发现CreateFile()函数中,可以在参数dwFlagsAndAttributes中指定FILE_FLAG_SEQUENTIAL_SCAN来优化顺序读的情况。那么,在LINUX中如何优化顺序读呢?
   查了一下资料,发现madvise()这个函数可以对映射的内存提出使用建议,从而提高内存。
   以下是我进行对比测试的代码,可以明显的发现:使用MADV_SEQUENTIAL标志比使用MADV_RANDOM标志更快。

   以下是代码:
=============================================================
//test_advise.cpp
//测试madvise()函数的差异
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <time.h>
#include <sys/time.h>

#ifndef P
#define P(format, ...) do \
    { \
        fprintf(stdout, "%s %s %d " format "\n", __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
        fflush(stdout); \
    } \
    while (0);
#endif

static int operator-(struct timeval& lsh, struct timeval& rsh)
{
    if (lsh.tv_sec==rsh.tv_sec)
    {
        return lsh.tv_usec - rsh.tv_usec;
    }
    else
    {
        return (lsh.tv_sec-rsh.tv_sec)*1000000 + (lsh.tv_usec - rsh.tv_usec);
    }
}

#define FILE_LEGNTH (200*1024*1024)    /*200MB的文件*/

/// 创建一个大的空文件
void CreateFile()
{
    int fd = open("test.dat", O_CREAT | O_RDWR);
    if (fd<1)
    {
        P("open error");
        return;
    }
    lseek(fd, FILE_LEGNTH-1, SEEK_SET);
    write(fd, "\0", 1);
    close(fd);
    fd = -1;
}

/// 顺序读
void SequenceRead()
{
    int fd = open("test.dat", O_RDWR | O_EXCL);
    if (-1==fd)
    {
        P("open error");
        return;
    }
    void* p = mmap(NULL, FILE_LEGNTH, PROT_READ, MAP_SHARED, fd, 0);
    if (MAP_FAILED==p)
    {
        P("map error");
        return;
    }
    close(fd);
    fd = -1;
    //内存使用建议
    if (-1==madvise(p, FILE_LEGNTH, MADV_WILLNEED | MADV_SEQUENTIAL))
    {
        P("madvise error");
        return;
    }
    //下面开始逐个字符读取
    struct timeval start;
    struct timeval end;
    int i;
    unsigned char* pTemp = (unsigned char*)p;
    int nSum = 0;
    gettimeofday(&start, NULL);
    for (i=0; i<FILE_LEGNTH; i++)
    {
        nSum += *pTemp;
        pTemp++;
    }
    gettimeofday(&end, NULL);
    //
    if (-1==munmap(p, FILE_LEGNTH))
    {
        P("unmap error");
        return;
    }
    //显示占用时间
    struct tm stTime;
    localtime_r(&start.tv_sec, &stTime);
    char strTemp[40];
    strftime(strTemp, sizeof(strTemp)-1, "%Y-%m-%d %H:%M:%S", &stTime);
    printf("start=%s.%07d\n", strTemp, (int)start.tv_usec);
    //
    localtime_r(&end.tv_sec, &stTime);
    strftime(strTemp, sizeof(strTemp)-1, "%Y-%m-%d %H:%M:%S", &stTime);
    printf("end =%s.%07d\n", strTemp, (int)end.tv_usec);
    printf("spend=%d 微秒\n", end-start);
}

/// 随机读
void RandomRead()
{
    int fd = open("test.dat", O_RDWR | O_EXCL);
    if (-1==fd)
    {
        P("open error");
        return;
    }
    void* p = mmap(NULL, FILE_LEGNTH, PROT_READ, MAP_SHARED, fd, 0);
    if (MAP_FAILED==p)
    {
        P("map error");
        return;
    }
    close(fd);
    fd = -1;
    //内存使用建议
    if (-1==madvise(p, FILE_LEGNTH, MADV_RANDOM))
    {
        P("madvise error");
        return;
    }
    //下面开始逐个字符读取
    struct timeval start;
    struct timeval end;
    int i;
    unsigned char* pTemp = (unsigned char*)p;
    int nSum = 0;
    gettimeofday(&start, NULL);
    for (i=0; i<FILE_LEGNTH; i++)
    {
        nSum += *pTemp;
        pTemp++;
    }
    gettimeofday(&end, NULL);
    //
    if (-1==munmap(p, FILE_LEGNTH))
    {
        P("unmap error");
        return;
    }
    //显示占用时间
    struct tm stTime;
    localtime_r(&start.tv_sec, &stTime);
    char strTemp[40];
    strftime(strTemp, sizeof(strTemp)-1, "%Y-%m-%d %H:%M:%S", &stTime);
    printf("start=%s.%07d\n", strTemp, (int)start.tv_usec);
    //
    localtime_r(&end.tv_sec, &stTime);
    strftime(strTemp, sizeof(strTemp)-1, "%Y-%m-%d %H:%M:%S", &stTime);
    printf("end =%s.%07d\n", strTemp, (int)end.tv_usec);
    printf("spend=%d 微秒\n", end-start);
}

int main()
{
    CreateFile();
    RandomRead();
    SequenceRead();
    return 1;
}

/*
g++ -o test_advise.o -c test_advise.cpp -g -Wall -Werror
g++ -o test_advise test_advise.o
*/
=============================================================

     需要注意的是,当没有使用madvise的时候,相当于使用madvise(... MADV_NORMAL),这种情况下仍然会进行预读,性能和使用madvise(... MADV_SEQUENTIAL)差不多。我猜想,使用MADV_SEQUENTIAL标志的时候,对于实际的物理内存的占用将是最小的,关于这点,希望内核高手能够给我些指点,谢谢!

猜你喜欢

转载自blog.csdn.net/stn_lcd/article/details/80731978