在 files/directories 上设置时间戳非常慢
Setting Timestamps on files/directories is extremely slow
我正在做一个项目,它需要复制大量文件和目录,同时保留它们的原始时间戳。因此,我需要多次调用目标的 SetCreationTime()
、SetLastWriteTime()
和 SetLastAccessTime()
方法,以便将原始值从源复制到目标。正如下面的屏幕截图所示,这些简单的操作占用了总计算时间的 42%。
由于这极大地限制了我的整个应用程序的性能,我想加快速度。我假设,这些调用中的每一个都需要打开和关闭 file/directory 的新流。如果是这个原因,我想让这个流保持打开状态,直到我完成所有属性的编写。我该如何做到这一点?我想这需要使用一些 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# 程序员,我不知道那些 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;
我已经看到设置文件时间的昂贵部分是文件的 opening/closing。大约 60% 的时间用于打开文件,大约 40% 的时间用于关闭文件(这需要将修改刷新到光盘)。上面的循环 10000 次迭代花费了大约 9s。
一项小研究表明,使用 FILE_WRITE_ATTRIBUTES
(而不是 GENERIC_WRITE
)调用 CreateFile(..)
足以更改文件的时间属性。
此修改显着加快了速度!现在相同的循环在 2 秒内完成 10000 次迭代。由于迭代次数非常少,我用 100000 次迭代进行了第二次 运行 以获得更可靠的时间测量:
- FILE_WRITE_ATTRIBUTES:5 运行s 100000 次迭代:12.7-13.2s
- GENERIC_WRITE:5 运行s 100000 次迭代:63.2-72.5s
根据以上数字,我猜测是 C# 方法在打开文件时使用了错误的访问模式以更改为文件时间。或者其他一些 C# 行为会减慢速度...
那么也许解决您的速度问题的方法是实现一个导出 C 函数的 DLL,该函数使用 SetFileTime(..)
更改文件时间?或者您甚至可以直接导入函数 CreateFile(..)
、SetFileTime(..)
和 CloseHandle(..)
以避免调用 C# 方法?
祝你好运!
我正在做一个项目,它需要复制大量文件和目录,同时保留它们的原始时间戳。因此,我需要多次调用目标的 SetCreationTime()
、SetLastWriteTime()
和 SetLastAccessTime()
方法,以便将原始值从源复制到目标。正如下面的屏幕截图所示,这些简单的操作占用了总计算时间的 42%。
由于这极大地限制了我的整个应用程序的性能,我想加快速度。我假设,这些调用中的每一个都需要打开和关闭 file/directory 的新流。如果是这个原因,我想让这个流保持打开状态,直到我完成所有属性的编写。我该如何做到这一点?我想这需要使用一些 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# 程序员,我不知道那些 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;
我已经看到设置文件时间的昂贵部分是文件的 opening/closing。大约 60% 的时间用于打开文件,大约 40% 的时间用于关闭文件(这需要将修改刷新到光盘)。上面的循环 10000 次迭代花费了大约 9s。
一项小研究表明,使用 FILE_WRITE_ATTRIBUTES
(而不是 GENERIC_WRITE
)调用 CreateFile(..)
足以更改文件的时间属性。
此修改显着加快了速度!现在相同的循环在 2 秒内完成 10000 次迭代。由于迭代次数非常少,我用 100000 次迭代进行了第二次 运行 以获得更可靠的时间测量:
- FILE_WRITE_ATTRIBUTES:5 运行s 100000 次迭代:12.7-13.2s
- GENERIC_WRITE:5 运行s 100000 次迭代:63.2-72.5s
根据以上数字,我猜测是 C# 方法在打开文件时使用了错误的访问模式以更改为文件时间。或者其他一些 C# 行为会减慢速度...
那么也许解决您的速度问题的方法是实现一个导出 C 函数的 DLL,该函数使用 SetFileTime(..)
更改文件时间?或者您甚至可以直接导入函数 CreateFile(..)
、SetFileTime(..)
和 CloseHandle(..)
以避免调用 C# 方法?
祝你好运!