寻找最快的大文件拷贝方法

  • 上传时间:2009-03-03 11:25:42
  • 点击此处下载
  • 文件说明:

     

      众所周知微软的操作系统自带的拷贝是很弱智的,速度不高,无断点续传,而且拷贝会拖累其他的应用程序,占用大量的文件缓存。所以很多高级的拷贝工具孕育而生,用过最好的是FastCopyFastCopy的拷贝速度基本上可以达到磁盘的极限,还因为他开源,所以可以看到其实现。但是很可惜他的工程是VC6的,而且源代码注释都是日文的,不仅如此,其源代码风格很让人迷惑。证实了我的那句话:开源软件的最高境界就是,我开源了,你看不懂;等你看懂了,已经过时了。
      要达到最快的拷贝速度和减少对内存的占用,需要对拷贝的过程有一个了解。拷贝无非就是将文件的数据读出来,然后再写进去的一个过程。XP操作系统自带的拷贝工具会首先打开文件句柄,然后将一块数据读取到缓存中,然后再写入到磁盘中。打开“Windows任务管理器,进程,查看,选择列,打开I/O读取字节,I/O写入字节。拷贝一个文件,注意explorer.exe进程即可看到整个读写过程。基本上可以看到XP对于文件拷贝几乎是属于同时进行的,换句话说其开的缓存比较小,但其效率可能并不见得很高。在我的200G Seagate 7200.8硬盘上,复制速度在15M/s左右。而这个硬盘的平均读取速度在40M/s,平均写入速度也在35M/s以上。
      在Vista下面文件拷贝做了一些优化,虽然一些BUG导致复制小文件会感觉很慢,但是复制大文件的思路已经不同于XP了。还是打开任务管理器,进行同样的操作。会发现Vista的会读取将近100M后,再将文件写入磁盘。explorer.exe进程也会在拷贝的瞬间内存占用飙升到100M以上,我的电脑商测试是120M左右,而复制完成以后内存占用将恢复正常。Vista的状态显示复制速度在18M/s左右。还是没有达到硬盘的极限速度。
    观察VistaXP的拷贝过程可以得出一个结论,Vista试图对磁盘的拷贝做优化了,但是其无论XP的分小块的复制,还是Vista的大缓存大块复制,都不能达到磁盘的最快速度。
      在两个操作系统复制的过程中,你会发现一个有趣的现象。XP任务管理器性能页面种的物理内存种的系统缓存的值会不断的增大,大到一个值以后就不会在增长。系统缓存主要用于缓存使用过的一些程序的内存、缓存打开并读写过的文件,已达到更快的读写速度。Win32 APICreateFile函数默认是使用系统缓存的读写,所以简单的用CreateFile打开的文件是要先到系统缓存的。explorer也是这样,所以当你打开了比较多的后台程序,复制完一个大文件以后,再打开这些后台程序就变得十分缓慢,硬盘不停的读取。这是因为文件缓存占用了太多的内存空间的缘故,将一些程序缓存占用了,所以后台程序会变得十分缓慢。Vista下面这种情况要好一些。虽然这样的设计可以加速很多文件操作的应用,但是对于文件拷贝这样的一次性操作,使用系统缓存固然就是浪费资源了。
      我还发现当使用FastCopy的拷贝大文件的时候会出现另一个现象,就是系统缓存会骤降,磁盘读写速度基本达到极限。在XP下面能够改善后台程序的性能,因为此时FastCopy使用的是不使用操作系统缓存的读写操作,软件自生打开了一个32M的缓存(可自定义)。Vista下面的行为有些古怪,系统缓存也会减少,但是当复制完以后,硬盘会不断的读取,直到达到复制之前的大小,XP无此现象。
    那么怎么样才能达到极限速度呢?是需要缓存还是不需要缓存呢?要缓存需要多大的缓存才好呢?为此我做了一个小实验。
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using Microsoft.Win32.SafeHandles;
    using System.Runtime.InteropServices;


    namespace csharp
    {
        
    class Program
        {
            
    public const short FILE_ATTRIBUTE_NORMAL = 0x80;
            
    public const short INVALID_HANDLE_VALUE = -1;
            
    public const uint GENERIC_READ = 0x80000000;
            
    public const uint GENERIC_WRITE = 0x40000000;
            
    public const uint CREATE_NEW = 1;
            
    public const uint CREATE_ALWAYS = 2;
            
    public const uint OPEN_EXISTING = 3;
            
    public const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
            
    public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;
            
    public const uint FILE_SHARE_READ = 0x00000001;
            
    public const uint FILE_SHARE_WRITE = 0x00000002;

            
    // Use interop to call the CreateFile function.
            // For more information about CreateFile,
            // see the unmanaged MSDN reference library.
            [DllImport("kernel32.dll", SetLastError = true)]
            
    static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
              
    uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
              
    uint dwFlagsAndAttributes, IntPtr hTemplateFile);

           
    static void Main(string[] args)
            {
                
    bool useBuffer = false;
                SafeFileHandle fr = CreateFile("d:source", GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, useBuffer? 0:FILE_FLAG_NO_BUFFERING , IntPtr.Zero);
                SafeFileHandle fw = CreateFile("d:dest", GENERIC_WRITE, FILE_SHARE_READ, IntPtr.Zero, CREATE_ALWAYS,useBuffer? 0:(FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH), IntPtr.Zero);

                
    int bufferSize = useBuffer? 1024*1024*32:1024 * 1024 * 32;

                FileStream fsr = 
    new FileStream(fr, FileAccess.Read);
                FileStream fsw = 
    new FileStream(fw, FileAccess.Write);

                BinaryReader br = 
    new BinaryReader(fsr);
                BinaryWriter bw = 
    new BinaryWriter(fsw);

                
    byte[] buffer = new byte[bufferSize];
                Int64 len = fsr.Length;
                DateTime start = DateTime.Now;
                TimeSpan ts;
                
    while (fsr.Position < fsr.Length)
                {
                    
    int readCount = br.Read(buffer, 0, bufferSize);
                    bw.Write(buffer, 0, readCount);
                    ts = DateTime.Now.Subtract(start);
                    
    double speed=(double)fsr.Position / ts.TotalMilliseconds * 1000 / (1024 * 1024);
                    
    double progress=(double)fsr.Position / len * 100;
                    Console.WriteLine("Speed:{0},  Progress:{1}",speed ,progress );
                }
                br.Close();
                bw.Close();
                sw.Close();
                Console.WriteLine("End");
                Console.ReadLine();
            }
        }
    }

      整个程序的思路比较简单,打开文件,读取数据到自定义的缓存,然后写入数据,关闭文件。.NET默认的FileStream默认是缓存的读写,而且没有参数指定非缓存的读写。但好在FileStream的一个构造函数能够很方便的传递一个句柄并为之所用。查阅MSDNSafeFileHandle时发现了CreateFile的用法,只需要传递一个FILE_FLAG_NO_BUFFERINGCreateFile,就可以实现不使用系统缓存的读写。但使用非缓存的读写有一些操作上的限制,详细的可以见MSDN的相关文档。
      运行以上程序,对一个Segate 80G的硬盘的D盘(硬盘自身缓存8M,其拷贝极限速度在26M/s左右)的一个大文件进行复制操作,得到的结果如下:
    缓存大小
    不使用系统缓存
    拷贝速度(MB/s
    使用系统缓存
    拷贝速度(MB/s)
    1M
    11.99
    n/a
    2M
    15.19
    n/a
    4M
    20.42
    n/a
    8M
    23.87
    n/a
    16M
    25.09
    n/a
    32M
    25.93
    11.31
    64M
    n/a
    15.89
    128M
    n/a
    17.02
    由于将在程序中将bufferSize设置为64M会导致出现异常,所以64M没有数据。对于使用缓存的拷贝的速度小于32M缓存的读取速度很慢,没有进行更多的测试。
      结果很明显,在同样的自定义缓存大小的同时,不使用系统缓存的拷贝速度明显要高于使用系统缓存的拷贝速度。当不使用系统缓存的拷贝时,当缓存大小等于磁盘物理缓存大小的时候拷贝速度就达到了90%的最大速度;当等于磁盘物理缓存2倍时基本达到磁盘存取极限。由于上述原因,这也就是为什么FastCopy不使用系统缓存的缘故了。
      当然如果大家有什么更好的看法或想法,欢迎留言探讨,谢谢!
    备份策略很重要,可是用户都很少在意。。。
    在实际的IT工作环境中,经常存在着具有如下特点的数据环境:
    1 文件的数量非常巨大,也就是说存在着数量庞大的独立文件;
    2 每个独立文件的数据量很小;
    3 数据变化并不频繁,特别是改变每个独立文件的频率相对很低。
        面对这种环境,使用常规的增量备份或者差异备份方式,不太容易取得完美的效果。这是由于常规备份方式判断文件是否发生改变的方式造成的。常规备份方式判断文件是否改变,通常采用如下的判断方式:
           当备份或者过期请求发起时:
    1 客户端程序搜索整个客户端需要备份的文件系统以建立一个文件特性描述列表;
    2 服务器端程序搜索整个文件空间,建立一个服务器端的的文件特性列表;
    3 比较这两个列表,如果某一个文件符合备份或者过期的条件,则执行相应的操作。例如该文件仅存在于客户端,但不存在于服务器端,则进行相应的备份操作。
        显然,当用户客户端的数据属于大量小数据环境的时候,再沿用这种判断方式,肯定会有相当的时间会花费在文件特性列表的建立和比较过程中。
        针对这种情况,IBM Tivoli Storage Manager(以下简称TSM),采用了一种基于日志的备份方式(Journal Based Bakckup)。当采用这种方式的时候,用户需要在备份客户端启动日志服务引擎(Journal Service Engine)。该引擎会记录客户端自上次工作结束后,发生改变的所有独立文件。因此,当进行备份的时候,备份客户端会直接从日志服务中获得要备份数据的信息,避免了两个文件特性列表的建立和比较工作,从而极大的提高备份处理速度。当此次备份完成后,日志服务会自动清除独立文件登记的信息,并继续跟踪客户端文件以后的变化。
        目前TSM基于日志的备份方式的适用平台为Windows NTWindows2000Windows 2003Window XP。需要注意的是在TSM 5.2.2版本中,Windows 2003 64 bit的客户端不支持基于日志的备份。
        实现基于日志的备份,需要在客户端启动日志服务,并预留日志数据库所需要的空间。启动日志服务的方式有两种,一种是使用命令行工具dsmcutil;一种是使用图形化的配置工具。
        关于dsmcutil的使用方式,用户可以在命令行中敲入:
           dsmcutil help
        获取命令的格式。
        由于基于日志的服务主要应用在Windows平台上,所以建议用户使用图形化的工具来进行配置。
        使用图形化的配置工具,用户可以在进入Windows备份归档客户端工具后,点击:UtilitiesSetup Wizard,系统弹出如下图的窗口:  
        选择配置Journal Engine,然后进入下一步。
        在这个向导的帮助下,用户可以非常容易的完成日志引擎和日志数据库的配置。当完成这一工作后,用户所进行的配置存储在tsmjbbd.ini文件中。
        当完成配置,并启动日志引擎后,用户以后所做的Incremental备份就全部都是基于日志的备份。
        一般说来,如果使用基于日志的备份,用户需要注意以下几点:
    1 一般说来至少运行一次Full Incremental备份,这样日志服务可以记录所有的文件状态信息,并生成相应的数据库;
    2 用户不能随意改动服务器端对应的文件空间,否则有可能使日志服务所记录的信息不准确;
    3 客户端由于在发起备份之前,不会查询服务器端的信息,所以,将忽略注入备份频率等服务器端维护的数据。
     
     
     


    现在有一个应用,一台机器大概200万张2M大小的文件,要读到一个集群中处理,或者copy到另外一个地方,发现速度特别慢。只有520MBs(网络)本地读取60Ms左右。
    这台机器配置:
    intel 5130*2
    memory 4*2G
    下边挂了 16*750G硬盘。(数据在这里边)
    系统:redhat 4 kernel 2.6.9-44
    文件系统:ext3
    我试验过不同的块大小,软硬raid,本来作了一个并行的文件系统(lustre),发现对小文件只有10M每秒的读写速度。(双千兆网卡bonding,一个单独的大文件在lustre里边有 900MBs的速度,所以可以排除是网络的问题)拆掉lustre用单台机器试验发现大文件都还好,就是这种小文件特别慢。用过ext3(没法格式化超过4T的),xfs两种文件系统
    效果一样,不知道问题在哪里,或者有其他解决方法。现在我也在试验中。哪位有这方面的资料或者经验给我点谢谢了。

北京正高集群信息科技有限公司-服务与支持

联系我们

  • 地址:北京市海淀区上地信息路30号上地大厦七层7019
  • 邮编:100085
  • 电话:010-51268258
  • 传真:010-51268258转8003
  • Email:zghpc@yahoo.com.cn