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

wod*_*dzu 3 c# filesystems io winapi

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

绩效分析

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

更新:

我跟着卢卡斯建议使用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);
        }
}
Run Code Online (Sandbox Code Playgroud)

我可以在这里找到我使用的枚举.这使我能够只打开文件一次完成所有事情:创建文件,应用所有属性,设置时间戳并从原始文件中复制实际内容.

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);
}
Run Code Online (Sandbox Code Playgroud)

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

优化前的结果:

之前

优化后的结果:

后

Luk*_*sen 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;
Run Code Online (Sandbox Code Playgroud)

我已经看到设置文件时间的昂贵部分是文件的打开/关闭.大约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#行为会减慢速度......

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

祝好运!