【发布时间】:2019-01-26 16:49:50
【问题描述】:
在我们的应用程序中,我们必须经常在远程机器(在同一个 LAN 段中)上写入大文件(100 MBytes 到几 GBytes)。这是我们应用程序的瓶颈。远程机器可以是本机 Windows,也可以是使用 SMB 的 Linux 机器。
我们发现,首先在本地创建文件,然后使用 Windows API 函数 CopyFile 复制它们比直接使用 CreateFile 和针对远程计算机的 UNC 路径(或驱动器号)快得多。但是我们仍然必须执行 2 次写入,这似乎远非最佳。
受第一个 cmets 关于这个问题的启发,我为 CreateFile 实现了 FILE_FLAG_OVERLAPPED 的使用,如 here 和 here 所讨论的:
HANDLE hToken;
auto openResult = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
if (!openResult)
{
gConsoleAndLog << "OpenProcessToken failed with err " << GetLastError() << std::endl;
}
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 3;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[2].Attributes = SE_PRIVILEGE_ENABLED;
if(! LookupPrivilegeValue(NULL, SE_MANAGE_VOLUME_NAME, &tp.Privileges[0].Luid))
gConsoleAndLog << "LookupPrivilegeValue SE_MANAGE_VOLUME_NAME failed with err " << GetLastError() << std::endl;
if (! LookupPrivilegeValue(NULL, SE_INCREASE_QUOTA_NAME, &tp.Privileges[1].Luid))
gConsoleAndLog << "LookupPrivilegeValue SE_INCREASE_QUOTA_NAME failed with err " << GetLastError() << std::endl;
if (! LookupPrivilegeValue(NULL, SE_ASSIGNPRIMARYTOKEN_NAME, &tp.Privileges[2].Luid))
gConsoleAndLog << "LookupPrivilegeValue SE_ASSIGNPRIMARYTOKEN_NAME failed with err " << GetLastError() << std::endl;
auto adjustResult = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL);
if (!adjustResult || GetLastError() != ERROR_SUCCESS)
{
gConsoleAndLog << "AdjustTokenPrivileges failed with err " << GetLastError() << std::endl;
}
else gConsoleAndLog << "AdjustTokenPrivileges SUCCESS" << std::endl;
与第二篇文章不同的是,即使以管理员身份启动,我也无法设置权限“SE_ASSIGNPRIMARYTOKEN_NAME”。我不知道这是否会有所不同。
使用 FILE_FLAG_NO_BUFFERING 打开文件后 | FILE_FLAG_OVERLAPPED,计算出的大小是预分配的:
auto setFileErr = SetFilePointerEx(hFile, endPosition, NULL, FILE_BEGIN);
if (setFileErr == INVALID_SET_FILE_POINTER)
{
CPrintWithOSError(NULL, 0, "SetFilePointerEx FAILED");
return 1;
}
if (!SetEndOfFile(hFile))
{
CPrintWithOSError(NULL, 0, "SetEndOfFile FAILED");
return 1;
}
if (!SetFileValidData(hFile, endPosition.QuadPart))
{
CPrintWithOSError(NULL, 0, "SetFileValidData FAILED");
return 1;
}
这适用于本地驱动器,但 SetFileValidData 在远程驱动器上失败。
调用失败并出现 windows 错误
1314 a required privilege is not held by the client
- 如何解决这个问题?
- 还有哪些其他方法可以做到这一点?
- 有没有 使用 WinAPI 为追加写入增加文件缓冲的方法?
【问题讨论】:
-
您在每个
WriteFile调用中写入了多少数据? -
与其说我们使用 Tifflib 来编写文件。一些 KB 可能 - 这是我们无法更改的。
-
那么目前的传输速度是多少?网络速度是多少?文件被压缩了吗?
-
为什么远程创建文件比本地创建和复制慢的最可能的解释是写出文件内容时缓冲效率低下。在生成输出时调整和优化内部缓冲应该可以解决这个问题。文件副本使用较大的缓冲区大小,以最大限度地减少开销。
-
一个简单的测试是对 IO 使用纯 C API 而不是
CreateFile/WriteFile,使用setvbuf添加一个大的(比如 32 MB?128 MB ?) 中间缓冲区。
标签: c++ performance winapi smb