【问题标题】:Setting Timestamps on files/directories is extremely slow在文件/目录上设置时间戳非常慢
【发布时间】:2015-05-06 13:38:13
【问题描述】:

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

由于这极大地限制了我的整个应用程序的性能,我想加快速度。我假设,这些调用中的每一个都需要打开和关闭文件/目录的新流。如果是这个原因,我想让这个流保持打开状态,直到我完成所有属性的编写。我该如何做到这一点?我想这需要使用一些 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);
}

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

优化前的结果:

优化后的结果:

【问题讨论】:

标签: c# winapi io filesystems


【解决方案1】:

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

这是我的基准循环的代码 sn-p:

#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% 用于关闭文件(这需要将修改刷新到磁盘)。上述循环进行了 10000 次迭代大约需要 9 秒。

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

此修改显着加快了速度!现在相同的循环在 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# 方法?

祝你好运!

【讨论】:

  • 哇,感谢您的详细回答!我会尝试使用FILE_WRITE_ATTRIBUTES,这应该可以解决我的问题。
  • 无需编写 DLL 即可。您可以编写调用 Windows API 函数的 C# 函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-15
  • 1970-01-01
  • 1970-01-01
  • 2011-06-27
相关资源
最近更新 更多