以文本方式查看主题

-  咿思舞论坛  (http://bbs.145829.com/index.asp)
--  『网站资源』  (http://bbs.145829.com/list.asp?boardid=8)
----  用Windows的文件映射机制,实现大批量数据的快速存储-ASP教程,系统相关  (http://bbs.145829.com/dispbbs.asp?boardid=8&id=164)

--  作者:ysw829
--  发布时间:2009/8/21 15:59:43
--  用Windows的文件映射机制,实现大批量数据的快速存储-ASP教程,系统相关
上次做的电子相册软件,为了解决大文件读取速度慢的问题,使用了windows下的文件映射功能,使文件读取效率顿时得到了大幅度提升。(具体见:一个读取速度超快的filestream!)

最近在做的一款软件,又使用到了这个函数,不过这次的要求是这样的:

系统的主程序会持续的从网络端口上接收数据,这些数据需要变为实时的图像显示,但是同时图像显示部分又需要有回顾功能,也就是说能够任意将历史的数据调出来显示,为此就必须将所有历史数据保存下来,同时在需要的时候又能够快速从历史数据的指定位置将数据读出来。

针对此,有两种方案:
1)在主程序所在的机器接收数据前,使用另一台电脑,配合一个转发数据的程序,来达到数据的截取功能,由这个转发数据的程序将所有数据存储下来,并在主程序需要使用时,再用网络发送给主程序。
2)使用主程序来进行数据存储,提高主程序存储数据的性能。

不管采用何种方案,最终的瓶颈都将是大数据量的快速保存。由于整个系统内存使用和速度上的要求都很高,因此不可能将这样的数据放在程序内存里,也不可能使用普通的文件方式来记录数据。最终看来只有求助于windows的文件映射功能了,它的优点是不会将要操作的文件读入内存,而是直接在系统层对文件进行读写,因此省去了文件的复制过程,特别是在读写大文件时,它能带来的速度提升是非常显著的。这样就可以将文件当作大内存来用了。

新的程序写完,才发现原来以前用delphi实现的那个版本根本不能够访问大文件,只是在读取文件速度上有些优势而已,因为我过去的做法是在createfilemapping()之后,将整个文件的内存都mapviewoffile()到程序内存里,这样文件一大,程序仍然无法打开文件。现在才知道,mapviewoffile()函数是可以指定从文件的哪个部分进行map,同时map多少进入内存的。对于真正的大文件(几个g的)的访问,因该是一块一块的map,然后进行读写。同时map的起始地址和map的内存大小都必须是64k的整数倍,不然mapviewoffile()函数会失败。

最初的一个简单的程序版本花了1个小时不到就写完了,但是一测试却发现有个严重的问题:没有考虑数据在map内存的边界上时的问题。当要读写的数据正好在map的内存的边界上时,从当前map的内存中就只能取到数据的前半部分,另一部分的数据必须map文件的下一块地址才可能取到。因此对程序又进行了彻底的改造,让其能够在读取一个map内存发现不够时,自动打开下一个map内存。

总算大功告成,写了一个简单的测试程序对其进行测试,速度和正确性都都非常理想。
最后,贴上程序代码:

#ifndef enstfilecache_h

#define enstfilecache_h

#include <qtcore/qvariant>
#include <qtcore/qobject>

#include <windows.h>

#include "../enstdefine.h"

/*! \\brief sampler::enstfilecache
     \\author tony (http://www.tonixsoft.com)
     \\version 0.08
     \\date 2006.05.10
    
    基于文件镜像的快速大容量数据存储类。该类使用windows下的 createfilemapping() 函数实现,不支持其它系统。\\n
    该类中的文件镜像原理可以参考:http://www.juntuan.net/hkbc/winbc/n/2006-04-19/14320.html \\n
    当要读取或写入的数据跨多个mapview的时候,该类会自动处理mapview的切换。
*/
class enstfilecache : public qobject
{
    q_object

public:
    /*!
        construct the class.
    */
    enstfilecache();

    /*!
        destruct the class.
    */
    ~enstfilecache();

    /*!
        打开镜像文件。
        @return 当打开镜像文件失败时返回false,比如磁盘空间不够。
    */
    bool createfilecache(const qstring &pfilename);

    /*!
        向镜像文件中追加数据。
    */
    bool appenddata(t_int8* pdata, int pdatalength);

    /*!
        从镜像文件的指定位置读取数据。
    */
    bool readdata(t_int64 paddressoffset, t_int8* pdata, int pdatalength);

protected:
    void dumpwindowserrormessage();

private:
    t_int64 mmappingviewsize;

    handle mfilehandle;

    handle mmappinghandle;

    t_int64 mwritemappingoffset;

    lpvoid mwritebuffer;

    t_int64 mwritebufferoffset;

    t_int64 mreadmappingoffset;

    lpvoid mreadbuffer;
};

#endif //enstfilecache_h

=====================================================


--  作者:ysw829
--  发布时间:2009/8/21 16:00:18
--  
#include "enstfilecache.h"

#include "../enstsvcpack.h"

//#define file_cache_size 0x40000000    /* = 1gb */
#define file_cache_size 0x01e00000  /* = 30mb */

enstfilecache::enstfilecache()
{
    mmappingviewsize = 1024*(64*1024); //windows default block size is 64kb

    mfilehandle = invalid_handle_value;
    mmappinghandle = null;
    mwritemappingoffset = 0;
    mwritebuffer = null;
    mwritebufferoffset = 0;
    mreadmappingoffset = 0;
    mreadbuffer = null;
}

enstfilecache::~enstfilecache()
{
    if (mwritebuffer != null) {
        unmapviewoffile(mwritebuffer);
    }
    if (mreadbuffer != null) {
        unmapviewoffile(mreadbuffer);
    }
    if (mmappinghandle != null) {
        closehandle(mmappinghandle);
    }
    if (mfilehandle != invalid_handle_value) {
        closehandle(mfilehandle);
    }
}

bool enstfilecache::createfilecache(const qstring &pfilename)
{
    enstlogservice *logservice = enstlogservice::getmyaddr();

    mfilehandle = createfile(pfilename.toascii(), generic_read | generic_write, file_share_read, null, open_always, file_attribute_normal, 0);
    if (mfilehandle == invalid_handle_value) {
        dumpwindowserrormessage();
        logservice->appendlog(this, "error when create file.");
        return false;
    }
    mmappinghandle = createfilemapping(mfilehandle, null, page_readwrite, (dword)(file_cache_size>>32), (dword)(file_cache_size & 0xffffffff), null);
    if (mmappinghandle == null) {
        if (getlasterror() == 112) {
            logservice->appendlog(this, "looks like its not enough space on the disk.");
        }
        dumpwindowserrormessage();
        logservice->appendlog(this, "error when create file mapping.");
        return false;
    }
    mwritemappingoffset = 0;
    mwritebuffer = null;
    mwritebufferoffset = 0;
    mreadmappingoffset = 0;
    mreadbuffer = null;
    return true;
}

bool enstfilecache::appenddata(t_int8* pdata, int pdatalength)
{
    int datawrote = 0;
    do {
        int datacanwrite = mmappingviewsize - mwritebufferoffset;
        if (mwritebuffer && datacanwrite <= 0) {
            unmapviewoffile(mwritebuffer);
            mwritebuffer = null;
            mwritemappingoffset += mmappingviewsize;
        }
        if (mwritebuffer == null) {
            mwritebuffer = mapviewoffile(mmappinghandle, file_map_write, (dword)(mwritemappingoffset>>32), (dword)(mwritemappingoffset & 0xffffffff), mmappingviewsize);
            mwritebufferoffset = 0;
            datacanwrite = mmappingviewsize - mwritebufferoffset;
            if (! mwritebuffer) {
                dumpwindowserrormessage();
                enstlogservice *logservice = enstlogservice::getmyaddr();
                logservice->appendlog(this, "error when map view of file.");
                return false;
            }
        }
        int datatowrite = pdatalength - datawrote;
        int actualdatatowrite = (datacanwrite >= datatowrite)?datatowrite:datacanwrite;
        memcpy((pbyte)mwritebuffer+mwritebufferoffset, (pbyte)pdata+datawrote, actualdatatowrite);
        mwritebufferoffset += actualdatatowrite;
        datawrote += actualdatatowrite;
    } while (datawrote < pdatalength);
    return true;
}


--  作者:ysw829
--  发布时间:2009/8/21 16:00:41
--  
bool enstfilecache::readdata(t_int64 paddressoffset, t_int8* pdata, int pdatalength)
{
    int datareaded = 0;
    do {
        int datacanread = mreadmappingoffset + mmappingviewsize - paddressoffset - datareaded;
        if (mreadbuffer && (datacanread <= 0 || datacanread > mmappingviewsize)) {
            unmapviewoffile(mreadbuffer);
            mreadbuffer = null;
        }
        if (mreadbuffer == null) {
            mreadmappingoffset = (paddressoffset + datareaded) / mmappingviewsize * mmappingviewsize;
            mreadbuffer = mapviewoffile(mmappinghandle, file_map_read, (dword)(mreadmappingoffset>>32), (dword)(mreadmappingoffset & 0xffffffff), mmappingviewsize);
            datacanread = mreadmappingoffset + mmappingviewsize - paddressoffset - datareaded;
            if (! mreadbuffer) {
                dumpwindowserrormessage();
                enstlogservice *logservice = enstlogservice::getmyaddr();
                logservice->appendlog(this, "error when map view of file.");
                return false;
            }
        }
        int datatoread = pdatalength - datareaded;
        int actualdatatoread = (datacanread >= datatoread)?datatoread:datacanread;
        memcpy((pbyte)pdata+datareaded, (pbyte)mreadbuffer+paddressoffset-mreadmappingoffset+datareaded, actualdatatoread);
        datareaded += actualdatatoread;
    } while (datareaded < pdatalength);
    return true;
}

void enstfilecache::dumpwindowserrormessage()
{
    lpvoid lpmsgbuf;
    dword dw = getlasterror();
    formatmessage(
        format_message_allocate_buffer |
        format_message_from_system,
        null,
        dw,
        makelangid(lang_neutral, sublang_default),
        (lptstr) &lpmsgbuf,
        0, null );
    enstlogservice *logservice = enstlogservice::getmyaddr();
    logservice->appendlog(this, (lptstr)lpmsgbuf);
    //puts((lptstr)lpmsgbuf);

    localfree(lpmsgbuf);
}

#ifdef win32
    #include "moc_enstfilecache.cpp"
#endif

由于系统是用qt开发的,因此类中使用了不少qt的类,同时也使用了系统中的部分工具类,不过基本工作原理相信你是能够看懂的。另外,整个系统是计划要跨平台使用的,因此今后还需要实现linux下的类似功能,目前这个版本被绑定在windows平台上了,不幸。