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

C# 在文件/目录上设置时间戳非常慢,c#,winapi,io,filesystems,C#,Winapi,Io,Filesystems,我正在做一个项目,需要复制很多文件和目录,同时保留它们原来的时间戳。因此,我需要多次调用目标的SetCreationTime()、SetLastWriteTime()和SetLastAccessTime()方法,以便将原始值从源复制到目标。如下面的屏幕截图所示,这些简单的操作占总计算时间的42% 由于这极大地限制了我的整个应用程序的性能,我想加快速度。我假设,每个调用都需要打开和关闭文件/目录的新流。如果这是原因,我想让这个流保持打开状态,直到我完成所有属性的编写。我如何做到这一点?我想这需要

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

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

更新:

我按照Lukas的建议使用WinAPI方法
CreateFile(..)
文件写入属性。为了P/调用上述方法,我创建了以下包装器:

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);
        }
}
可以找到我使用的枚举。这允许我只打开一次文件即可完成所有操作:创建文件、应用所有属性、设置时间戳以及从原始文件复制实际内容

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);
}
FileInfo目标文件;
int文件属性;
索引时间戳;
使用(var hFile=Win32ApiWrapper.CreateFileGetHandle(targetFile.FullName,attributeFlags))
使用(var targetStream=newfilestream(hFile,FileAccess.Write))
{
//复制文件
设置文件时间(hFile,时间戳);
}
值得付出努力吗?是的。它将计算时间从86秒减少到51秒,减少了约40%

优化前的结果:

优化后的结果:

我不是一名C#程序员,我不知道那些System.IO.FileSystemInfo方法是如何实现的。但是我已经用WIN32 API函数做了一些测试,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次迭代

一些研究表明,使用
FILE\u WRITE\u ATTRIBUTES
(而不是
GENERIC\u WRITE
)调用
CreateFile(..)
)足以更改文件的时间属性

这一修改大大加快了速度!现在,相同的循环在2秒内完成10000次迭代。由于迭代次数非常少,因此我进行了第二次运行,共进行了100000次迭代,以获得更可靠的时间度量:

  • 文件写入属性:5次运行,100000次迭代:12.7-13.2s
  • 通用写入:5次运行,100000次迭代:63.2-72.5s
根据以上数字,我猜测C#方法在打开文件更改为文件时间时使用了错误的访问模式。或者其他一些C#行为会让事情变慢

因此,解决速度问题的一个方法可能是实现一个DLL,该DLL导出一个C函数,该函数使用
SetFileTime(…)
更改文件时间?或者您甚至可以直接导入函数
CreateFile(..)
SetFileTime(..)
CloseHandle(..)
,以避免调用C方法


祝你好运

哇,谢谢你的详细回答!我将尝试使用
文件\u WRITE\u属性
,这将解决我的问题。无需编写这样做的DLL。您可以编写一个调用Windows API函数的C函数。有关
文件.SetCreationTimeUtc
的实现,请参阅。您可能希望编写一个P/调用的函数
CreateFile
,后跟
SetFileTime