一个1M的格式文本文件,每行存储时间和数值,之间用\t分开,行尾为0x0d0x0a
用CFile::Read读取,每次读8k
从中找出指定的行,遍历完所有行时间消耗:第一次:文件没被读过,不在系统磁盘缓冲区,耗时800毫秒
第二次到第n次,文件已在磁盘缓冲区,耗时8毫秒这说明99%的时间消耗在了磁盘IO上面。数据总要从磁盘读出来,内存映射方式肯定不行。关键是要提高IO性能,因为不能保证每个文件都在磁盘缓冲中。请教用哪个IO函数能提高磁盘IO性能呢?或者CFile打开方式是否对性能有影响,我是用二进制打开的。CFile::shareExclusive 方式也试过,对性能基本没影响。

解决方案 »

  1.   

    //lpszVarName 变量名
    //lpszDate 日期格式yyyymmdd
    //lpszStartTime 开始时间格式hhmmss
    //pc 接收缓冲区
    //nsize 缓冲大小
    //返回字节数,含结尾的0
    //功能,从开始时间起读一小时数据,当缓冲空间不够时可能小于一小时数据
    int CTxtDB::GetRecFromFile(LPCTSTR lpszVarName,LPCTSTR lpszDate,LPCTSTR lpszStartTime,char *pc,int nsize)//从文件读取记录
    {
    CSafeLock lock(&m_Lock);//加锁 CString szFile=m_szPrjPath + "\\" + lpszDate + "\\" + lpszDate + "_" + lpszVarName + ".txd"; int ntime = atol(lpszStartTime);
    int nh,nm,ns;

    nh = ntime /10000;
    nm = (ntime % 10000)/100;
    ns = (ntime % 10000)%100; nh += 1;
    if(nh > 23)
    {
    nh = 23;
    nm = 59;
    ns = 59;
    } char stimee[16];
    sprintf(stimee,"%02d%02d%02d",nh,nm,ns); CFile fl;
    if(!fl.Open(szFile,CFile::modeRead))//文件不能打开,一般是不存在
    return 0;//返回0字节

    char s1[32],s2[32]; int s1p=0,s2p=0;

    int i,k,n=fl.Read(m_sBolckBuf,8192);
    int npos=0;
    int np=0;
    char c;
    BOOL bStart = FALSE;
    while ( n > 0)
    {
    for(i=0;i<n;i++)
    {
    c= m_sBolckBuf[i];
    if(c=='\t')
    npos=1;
    else if((c==0x0d)||(c==0x0a))
    {
    npos =0;
    s1[s1p] = 0;
    s2[s2p] = 0; if((s1p>0)&&(s2p>0))
    {
    //处理一行
    if(bStart == FALSE)
    {
    if(strcmp(s1,lpszStartTime) < 0)
    {
    s1p = 0;
    s2p = 0;
    continue;//没到起始点
    }
    bStart = TRUE;
    }
    if(strcmp(s1,stimee) >= 0)//超过一小时 
    {
    *pc = 0;
    fl.Close();
    return np;
    }
    k=0;
    while(s1[k] != 0)
    {
    *pc++ = s1[k];
    np++;
    k++;
    }
    *pc++ = '\t';
    np++; k=0;
    while(s2[k] != 0)
    {
    *pc++ = s2[k];
    np++;
    k++;
    }
    *pc++ = 0x0d;
    np++; if(npos + 32 >= nsize)//超过缓冲大小
    {
    *pc=0;
    fl.Close();
    return np;
    }
    } s1p = 0;
    s2p = 0;
    }
    else
    {
    if(npos == 0)
    {
    if(s1p < 30)
    {
    s1[s1p] = c;
    s1p++;
    }
    }
    else
    {
    if(s2p < 30)
    {
    s2[s2p] = c;
    s2p++;
    }
    }
    }
    }//for
    if( n < 8192)
    break;
    n=fl.Read(m_sBolckBuf,8192);
    }//while
    if(np>0)
    *pc=0;
    fl.Close();
    return np;
    }
      

  2.   

    用CFile::ReadHuge一次读完也不会提高效率,因此操作系统底层已经处理了预读和缓存了。
      

  3.   

    磁盘I/O的主要时间消耗在等待设备操作上,通过异步I/O或者多线程可以利用等待时间来做其它工作。此外,可以考虑绕过磁盘I/O的中间过程,不过效果不是很明显。
      

  4.   

    从硬盘上一次性读1兆和读8K,虽然有差别,但是并没有想像的那么大,因为现在的操作系统会对IO操作进行优化。在dos下,这种差别才比较明显。
      

  5.   

    好像这个问题目前还没有效的方法
    3,4楼的方法没得效果,因为我做个实验的。
    5,6楼说的有道理,从机械磁盘读取数据固定时间是无法减少的(无缓存条件下)。如果从系统层没得办法,考虑的解决方法主要是减小文件大小。减少文件大小的方式包括换存储格式、采用LZW压缩两种方法。
      

  6.   

    从磁盘读数据时间包括寻道时间和读取时间,寻道时间基本固定在10ms内,读取时间和自己数是线性关系。
    因此只能从文件大小上解决。
    换文件格式,大小可减半,再用LZW压缩又可减少2/3,综合下来可减少文件从1M到200K左右,这样基本可提高5倍效率。
      

  7.   

    刚才测试,300k文件(无缓冲),只要110ms
      

  8.   

    是这样的
    我在服务器写了一个中间层程序,提供TCP服务,客户需要查询读取指定文件中某时间段的数据。一次服务最多通过TCP传输64K数据。
    以上测试时间实际包含了TCP传输时间,处理数据和TCP传输时间都很小,99%时间花在磁盘IO上了。为了提供更快速的数据服务和更多的同时连接客户,需要挖掘磁盘IO最大效率。简单的说就是服务方从指定磁盘文件检索客户需求的数据,最多一次传输64K数据,这个中间层程序要求尽量快。
      

  9.   

    的确处理时间和传输时间占用很小
    下面是客户端测试代码CTimeCount ct;//计时
    ct.start();
    nret = hdc.OpenRec("L101.ic","20080724","230000");
    ct.end();hdc.OpenRec函数返回时,已经将数据读到hdc对象的缓冲区了,以上代码客户从服务读取L101.ic 20080724 23点开始的1小时数据。服务器文件非缓冲时是800毫秒,缓冲时是8毫秒100:1的时间
      

  10.   

    计时部分应该足够准确的class   CTimeCount   
    {   
    public:   
       CTimeCount(){};   
       ~CTimeCount(){};   
      void start()
      {
    QueryPerformanceFrequency(&litmp);   
    dfFreq   =   (double)litmp.QuadPart;//   获得计数器的时钟频率   
    QueryPerformanceCounter(&litmp);   
    QPart1   =   litmp.QuadPart;//   获得初始值   
      };     //开始计时   
      void end()
      {
    QueryPerformanceCounter(&litmp);   
    QPart2   =   litmp.QuadPart;//获得中止值   
        dfMinus   =   (double)(QPart2-QPart1);   
    dfTim   =   dfMinus   /   dfFreq;//   获得对应的时间值,单位为秒 
      };         //停止计时      double time_milli(){
      return dfTim * 1000.0;
      };       //以毫秒精度输出所用时间   private:   
    LARGE_INTEGER   litmp;     
        LONGLONG   QPart1,QPart2;   
        double   dfMinus,   dfFreq,   dfTim;     
    };   
      

  11.   

    服务端数据处理
    //lpszVarName 变量名
    //lpszDate 日期格式yyyymmdd
    //lpszStartTime 开始时间格式hhmmss
    //pc 接收缓冲区
    //nsize 缓冲大小
    //返回字节数,含结尾的0
    //功能,从开始时间起读一小时数据,当缓冲空间不够时可能小于一小时数据
    int CTxtDB::GetRecFromFile(LPCTSTR lpszVarName,LPCTSTR lpszDate,LPCTSTR lpszStartTime,char *pc,int nsize)//从文件读取记录
    {
    CSafeLock lock(&m_Lock);//加锁 CString szFile=m_szPrjPath + "\\" + lpszDate + "\\" + lpszDate + "_" + lpszVarName + ".txd"; int ntime = atol(lpszStartTime);
    int nh,nm,ns;

    nh = ntime /10000;
    nm = (ntime % 10000)/100;
    ns = (ntime % 10000)%100; nh += 1;
    if(nh > 23)
    {
    nh = 23;
    nm = 59;
    ns = 59;
    } char stimee[16];
    sprintf(stimee,"%02d%02d%02d",nh,nm,ns); CFile fl;
    if(!fl.Open(szFile,CFile::modeRead))//文件不能打开,一般是不存在
    return 0;//返回0字节

    char s1[32],s2[32]; int s1p=0,s2p=0;

    int i,k,n=fl.Read(m_sBolckBuf,8192);
    int npos=0;
    int np=0;
    char c;
    BOOL bStart = FALSE;
    while ( n > 0)
    {
    for(i=0;i<n;i++)
    {
    c= m_sBolckBuf[i];
    if(c=='\t')
    npos=1;
    else if((c==0x0d)||(c==0x0a))
    {
    npos =0;
    s1[s1p] = 0;
    s2[s2p] = 0; if((s1p>0)&&(s2p>0))
    {
    //处理一行
    if(bStart == FALSE)
    {
    if(strcmp(s1,lpszStartTime) < 0)
    {
    s1p = 0;
    s2p = 0;
    continue;//没到起始点
    }
    bStart = TRUE;
    }
    if(strcmp(s1,stimee) >= 0)//超过一小时 
    {
    *pc = 0;
    fl.Close();
    np++;
    return np;
    }
    k=0;
    while(s1[k] != 0)
    {
    *pc++ = s1[k];
    np++;
    k++;
    }
    *pc++ = '\t';
    np++; k=0;
    while(s2[k] != 0)
    {
    *pc++ = s2[k];
    np++;
    k++;
    }
    *pc++ = 0x0d;
    np++; if(npos + 32 >= nsize)//超过缓冲大小
    {
    *pc=0;
    fl.Close();
    np++;
    return np;
    }
    } s1p = 0;
    s2p = 0;
    }
    else
    {
    if(npos == 0)
    {
    if(s1p < 30)
    {
    s1[s1p] = c;
    s1p++;
    }
    }
    else
    {
    if(s2p < 30)
    {
    s2[s2p] = c;
    s2p++;
    }
    }
    }
    }//for
    if( n < 8192)
    break;
    n=fl.Read(m_sBolckBuf,8192);
    }//while
    if(np>0)
    *pc=0;
    np++;
    fl.Close();
    return np;
    }
      

  12.   

    to
    unsigned1、锁不是锁文件的
    2、读1M文件一次读完和每次都8K实际效果是一样的,你可去做实验。
    3、不按记录存储原因:如果是当日文件,文件随时在增涨的。服务器上每日有上千个文件,每年该有多少,恐怕100G内存也缓存不过来的。
    4、实际测试瓶颈只是在磁盘IO,检索处理和TCP传输只占1%时间。要分析究竟瓶颈在哪里才能做好优化。
      

  13.   

    2.既然磁盘IO是一个瓶颈,又如何可能一次读完1MB会跟分而8K的多次读取效果一样?
      

  14.   

    如果真如你所说的一次性1MB读取和每8K分多次读取效果一样的话,那么你的磁盘的碎片状况不太乐观.
      

  15.   

    cnzdgs
    的方法有一定效果,但效率提升有限。
    目录:
    data/demo/20080721/L101.ia.txt
    ...
    data/demo/20080722/L101.ia.txt
    ...
    data/demo/20080724/L101.ia.txt
    ...
    data/demo/20080725/L101.ia.txt
    ...测试时,客户访问服务器程序时,服务程序其实已经访问过当日目录,并向data/demo/20080725/L101.ia.txt写入过数据,因此目录就只有最后的日子目录不同我还测试过第一次读data/demo/20080724/L101.ia.txt,第二次读data/demo/20080724/L101.ib.txt,同级目录的文件,差别并不大,就10几毫秒差别。为保证非缓冲,每次测试完后重新启动服务器。
      

  16.   

    unsinged
    谢谢您的热情回帖。数据库的性能瓶颈也在磁盘IO上SQL Server数据库中如果从100G中检索出某数据1小时数据(约3600条记录),恐怕1秒内不可能到达客户端的。而我现在的方法最大消耗时间是基本固定的,不管数据量,只要硬盘足够大,非缓冲条件下,1M也是800毫秒,320G也是800毫秒。而缓冲条件下最大只要8毫秒。写库就更快了,因为除第一次非缓冲外,其他都是缓冲。写库时间固定,平均每条记录写库时间0.25毫秒。当然是一次写多条记录。单独开的写库线程,时间足够及时写入的。
      

  17.   

    读300k的文件和读1M的文件时间差别很大,800毫秒和110毫秒的差别。
    原因如下:磁盘系统在操作系统的管理下,本来就是异步的,为提高效率,操作系统是一次读一块到缓存,至于这个快有多大,不知道(估计每块128K)
    但是知道的时读1M是的块数肯定要比读300K的块数多,块之间的时间间隔就取决于操作系统内系统进程、其他应用程序进程对磁盘
    的访问要求了,如果其他程序也在访问,单个程序读盘是块之间的间隔就大了,这才是耗时的正真原因。因此减小文件大小才是提高
    效率的方法。
      

  18.   

    速度慢应该是因为你每次只读8KB引起的,估计系统在读磁盘时没有预读,你每次读8KB系统就只读8KB,下次读文件时又再次读磁盘,浪费了时间。既然你每次发送64KB数据,如果异步读文件,应该每次读64KB,如果用同步访问文件,应该读更多,建议每次读1MB。
      

  19.   


    实验结果表明,一次读8K和读1M是一样的,因为读第一个8k时操作系统才开始预读为什么选8K
    因为我用1K,2K,4K,16K,64K,1M都做个实验,8K以后基本时间没得变化。第一读完文件,不管是每次读1个字节还是每次读1M字节,操作系统都缓存了。
    第二次读其实都是从缓冲读。对于同一个文件,不存才实际中每次都访问磁盘的现象,除非文件内容改变了。
      

  20.   

    这个效率是没有办法的,这个时间是必需消耗的,因为磁盘要转,要有操作系统的处理时间,因为当有open()时,操作系统要产生内核对象,所以时间比较慢,这个慢并不一定主要体现到IO上,而是操作系统的初始化操作所影响的
    你第一次读取时,操作系统必需要处理,这个过程不可能少去的
    就像操作指纹为了节省电能一样,有可能让磁盘低速转,这个时候你去操作,肯定会比较慢,但是一旦它正常转起来之后,速度就会快很多
      

  21.   

    我感觉你的各次实验中一定有某一次或多次的实验结果是不准确,有可能实验时受到了系统或其它因素的影响,因为各次实验的结果之间是存在矛盾的。
    如果说读300KB的文件需要110ms是正确的,那么读1MB的文件需要800ms这个数据就没有参考价值了,文件大小大约是1比3,所花时间的比例应该小于1比3才合理,如果是由于1MB的文件在磁盘中储存空间不连续,那就不该用这个文件来做实验了。
    另外你可以试试把一个文件复制一份,看看需要多少时间,可以用大一些的文件(例如100MB)来试,目前的硬件速度复制100MB文件应该不会用到80s时间(我的电脑上测试不到5s)。复制文件要执行读和写,所以复制文件的时间肯定比单纯读文件的时间要长。
      

  22.   

    类似提供数据服务的程序,应该尽可能多利用内存缓冲数据,比如你至少开上1~2G内存缓存,除了初次读取花点时间,运行过程中效率肯定高了。既然磁盘I/O是个瓶颈,编程人员就要绕过它,改变思路。说实话,速度和成本就是正比,成本越高速度越快,你要原意,用磁盘阵列、SCSI、RAID等,再看速度如何。
      

  23.   

    最新测试结果出来了
    用连续的没有碎片的文件测试
    1M大小文件(重起服务器保证非缓冲)
    客户发送请求开始,服务器包括打开读取检索将结果约64K发送到客户端共耗时40-50毫秒之间多次非缓冲测试,均在40-50之间,性能基本能满足要求了。看来是我写文件产生的碎片太多造成频繁寻道浪费了IO时间。接下来就是解决写文件碎片的问题,方法有两种。一是预置文件大小(很多BT下载软件采用这个方法),二是文件最后一条记录写入后转存到其他磁盘(比如两个sataII接口磁盘物理盘,一个当工作盘,保存当日数据,另一个当历史数据盘,当日最后一个记录写完后就转存到历史盘)。日数据可能上几个G,因此当日数据全部缓存到内存是不可能的。
      

  24.   

    磁盘碎片的确严重影响性能
    将测试文件重新拷贝后,保证每个文件连续,
    然后重启服务器,保证非缓冲
    下面客户端测试代码
    5个文件,大小在490K到520K之间,是平时每日生成文件的典型大小
    CTimeCount ct;//计时
    ct.start();
     
    nret = hdc.OpenRec("L101.ic","20080721","23000");
    nret = hdc.OpenRec("L101.ib","20080721","230000");
    nret = hdc.OpenRec("L101.ic","20080721","230000");
    nret = hdc.OpenRec("L101.p","20080721","230000");
    nret = hdc.OpenRec("L101.q","20080721","230000");

    ct.end();

    CString szTimeCount;
    szTimeCount.Format("%.6f毫秒",ct.time_milli()/5);

    MessageBox(szTimeCount);测试结果:每个文件平均耗时18.56毫秒,速度够用了,每秒可服务50个文件了。或者每秒可为50个用户提供一次服务了。如果数据在缓冲中,速度还可提高一倍。因生成的文件大小波动范围很大,因此不采用预置空间,而是采用双硬盘,一个硬盘单独存储历史数据,用一个线程来转存数据到历史盘,保证没得文件碎片。
      

  25.   

    此贴堪称经典~
    "用户对日志数据的访问",存在倾向性么,即有没有规律,是否满足局部性原理?
    如果用户倾向访问大量日志中的某一部分数据,可以考虑在内存中做一层Cache。减少访问IO的次数。
      

  26.   

    楼主的这个磁盘文件碎片问题,应该是由于在您的程序写入日志的过程中,机器上运行的其他进程也在写某些文件,造成您的日志跟其他文件的穿插。
    文件大小不固定,所以不使用预留。而使用重新拷贝,以消除文件碎片。这确实是个很实用的技术。以前只知道内存碎片会造成很多问题,比如Cache miss率下降,申大块内存不够等,这次看到了类似的文件碎片带来的问题,受益匪浅。学习了~
      

  27.   

    to gaoteng1984我的数据文件是交叉写入的,上千个数据文件交叉异步写入,每次写入小于4K,假设NTFS文件系统簇大小为4K,这样1M的文件就有256个碎片,所以碎片很严重。我准备采用预置文件大小和重新拷贝到另一个物理盘相结合的方式解决,预置时,每次按256K增量预置,这样1M文件碎片就只有4个,这对提高工作盘效率有好处,非当日数据拷贝到数据专用物理盘,保证连续,截掉数据文件多余空间,提高历史数据访问效率。至于缓存的事情就交给操作系统去做吧,从我测试结果来看,操作系统的缓存已经做得非常好了。