我正在处理一个项目,该项目需要复制大量文件和目录,同时保留其原始时间戳。因此,我需要对目标的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);
}
值得努力吗?是的。它将计算时间从86s减少到51s,减少了约40%。
优化前的结果:
优化后的结果:
答案 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次迭代运行,以获得更可靠的时间测量:
基于以上数字,我猜测C#方法在打开文件时使用了错误的访问模式以更改为文件时间。或者其他一些C#行为减慢了事情......
所以也许解决速度问题的方法是实现一个导出C函数的DLL,它使用SetFileTime(..)
来改变文件时间?或者您甚至可以直接导入函数CreateFile(..)
,SetFileTime(..)
和CloseHandle(..)
以避免调用C#方法?