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
。