设置文件/目录的时间戳非常慢

时间:2015-05-06 13:38:13

标签: c# winapi io filesystems

我正在处理一个项目,该项目需要复制大量文件和目录,同时保留其原始时间戳。因此,我需要对目标的SetCreationTime()SetLastWriteTime()SetLastAccessTime()方法进行多次调用,以便将原始值从源复制到目标。如下面的屏幕截图所示,这些简单的操作占用总计算时间的42%。

performance analysis

由于这极大地限制了我整个应用程序的性能,我想加快速度。我假设,每个调用都需要打开和关闭文件/目录的新流。如果这是原因,我想在打开所有属性之前将此流保持打开状态。我该如何做到这一点?我想这需要使用一些P / Invoke。

更新

我跟着 Lukas'建议将WinAPI方法CreateFile(..)FILE_WRITE_ATTRIBUTES一起使用。为了P / Invoke我创建的方法,我创建了以下包装器:

public class Win32ApiWrapper
{
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern SafeFileHandle CreateFile(string lpFileName,
                                                    [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
                                                    [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
                                                    IntPtr lpSecurityAttributes, 
                                                    [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
                                                    [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
                                                    IntPtr hTemplateFile);

    public static SafeFileHandle CreateFileGetHandle(string path, int fileAttributes)
    {
        return CreateFile(path,
                (FileAccess)(EFileAccess.FILE_WRITE_ATTRIBUTES | EFileAccess.FILE_WRITE_DATA),
                0,
                IntPtr.Zero,
                FileMode.Create,
                (FileAttributes)fileAttributes,
                IntPtr.Zero);
        }
}

我使用的枚举可以找到here。这使我只需打开一次文件即可完成所有操作:创建文件,应用所有属性,设置时间戳并从原始文件中复制实际内容文件。

FileInfo targetFile;
int fileAttributes;
IDictionary<string, long> timeStamps; 

using (var hFile = Win32ApiWrapper.CreateFileGetHandle(targetFile.FullName, attributeFlags))
using (var targetStream = new FileStream(hFile, FileAccess.Write))
{
    // copy file
    Win32ApiWrapper.SetFileTime(hFile, timeStamps);
}

值得努力吗?是的。它将计算时间从86s减少到51s,减少了约40%。

优化前的结果:

before

优化后的结果:

after

1 个答案:

答案 0 :(得分:6)

我不是C#程序员,我也不知道如何实现这些System.IO.FileSystemInfo方法。但我已经使用WIN32 API函数SetFileTime(..)进行了一些测试,C#会在某些时候调用它。

以下是我的基准测试循环的代码片段:

#define NO_OF_ITERATIONS   100000

int iteration;
DWORD tStart;
SYSTEMTIME tSys;
FILETIME tFile;
HANDLE hFile;
DWORD tEllapsed;


iteration = NO_OF_ITERATIONS;
GetLocalTime(&tSys);
tStart = GetTickCount();
while (iteration)
{
   tSys.wYear++;
   if (tSys.wYear > 2020)
   {
      tSys.wYear = 2000;
   }

   SystemTimeToFileTime(&tSys, &tFile);
   hFile = CreateFile("test.dat",
                      GENERIC_WRITE,   // FILE_WRITE_ATTRIBUTES
                      0,
                      NULL,
                      OPEN_EXISTING,
                      FILE_ATTRIBUTE_NORMAL,
                      NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      printf("CreateFile(..) failed (error: %d)\n", GetLastError());
      break;
   }

   SetFileTime(hFile, &tFile, &tFile, &tFile);

   CloseHandle(hFile);
   iteration--;
}
tEllapsed = GetTickCount() - tStart;

我已经看到设置文件时间的昂贵部分是文件的打开/关闭。大约60%的时间用于打开文件,大约40%用于关闭文件(需要将修改刷新到光盘)。上面的循环大约花了9秒进行10000次迭代。

一项小小的研究表明,使用CreateFile(..)(而非FILE_WRITE_ATTRIBUTES)调用GENERIC_WRITE足以更改文件的时间属性。

这种修改速度显着提升!现在,相同的循环在2秒内完成10000次迭代。由于迭代次数非常少,我已经进行了第二次100000次迭代运行,以获得更可靠的时间测量:

  • FILE_WRITE_ATTRIBUTES:5次运行,100000次迭代:12.7-13.2s
  • GENERIC_WRITE:5次运行,100000次迭代:63.2-72.5s

基于以上数字,我猜测C#方法在打开文件时使用了错误的访问模式以更改为文件时间。或者其他一些C#行为减慢了事情......

所以也许解决速度问题的方法是实现一个导出C函数的DLL,它使用SetFileTime(..)来改变文件时间?或者您甚至可以直接导入函数CreateFile(..)SetFileTime(..)CloseHandle(..)以避免调用C#方法?

祝你好运!