我有一个代码块,需要使用 UNC 路径从 NAS 服务器打开和读取大量小文本文件。 此代码是最初用 C++ 编写的模块的一部分,但现在正在转换为 C#。 C# 版本明显较慢。 我确定打开文件的调用几乎占了所有的性能差异。 使用WireShark,我发现这是因为System.IO.File.Open调用发出的SMB网络请求比类似的C++代码多得多。
C++代码进行以下调用:
FILE *f = _wfsopen(fileName, L"r", _SH_DENYWR);
这将生成以下 SMB 请求序列:
NT Create AndX Request, FID: 0x0004, Path: \a\i\a\q\~141106162638847.nmd
NT Create AndX Response, FID: 0x0004
Trans2 Request, QUERY_FILE_INFO, FID: 0x0004, Query File Basic Info
Trans2 Response, FID: 0x0004, QUERY_FILE_INFO
Read AndX Request, FID: 0x0004, 1327 bytes at offset 0
Read AndX Response, FID: 0x0004, 1327 bytes
Close Request, FID: 0x0004
Close Response, FID: 0x0004
NT Create AndX Request, FID: 0x0005, Path: \a\i\a\q\~141106162638847.nmd
NT Create AndX Response, FID: 0x0005
C# 代码进行此调用:
FileStream f = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
这将生成以下 SMB 请求序列:
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a\i\a\q\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \a\i\a\q\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a\i\a\q\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path:
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path:
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \a
Trans2 Response, FIND_FIRST2, Files: a
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \a\i
Trans2 Response, FIND_FIRST2, Files: i
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a\i
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \a\i
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \a\i\a
Trans2 Response, FIND_FIRST2, Files: a
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a\i\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \a\i\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \a\i\a\q
Trans2 Response, FIND_FIRST2, Files: q
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a\i\a\q\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \a\i\a\q\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a\i\a\q\~141106162638847.nmd
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path:
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path:
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \a
Trans2 Response, FIND_FIRST2, Files: a
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \a\i
Trans2 Response, FIND_FIRST2, Files: i
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a\i
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \a\i
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \a\i\a
Trans2 Response, FIND_FIRST2, Files: a
Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \a\i\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, QUERY_PATH_INFO, Query File Standard Info, Path: \a\i\a
Trans2 Response, QUERY_PATH_INFO
Trans2 Request, FIND_FIRST2, Pattern: \a\i\a\q
Trans2 Response, FIND_FIRST2, Files: q
Close Request, FID: 0x000f
Close Response
NT Create AndX Request, FID: 0x0018, Path: \a\i\a\q\~141106162638847.nmd
NT Create AndX Response, FID: 0x0018
Trans2 Request, QUERY_FILE_INFO, FID: 0x0018, Query File Basic Info
Trans2 Response, FID: 0x0018, QUERY_FILE_INFO
Read AndX Request, FID: 0x0018, 1327 bytes at offset 0
Read AndX Response, FID: 0x0018, 1327 bytes
Close Request, FID: 0x0018
Close Response, FID: 0x0018
NT Create AndX Request, FID: 0x0019, Path: \a\i\a\q\~141106162638847.nmd
NT Create AndX Response, FID: 0x0019
为什么System.IO.File.Open会发出所有这些额外的SMB请求? 有没有办法更改此代码以避免所有这些额外的请求?
简而言之,File.Open 调用new FileStream()
,new FileStream()
会做很多调用:
-
规范化路径。
String filePath = Path.NormalizePath(path, true, maxPath); // fullCheck: true
导致此代码:
1.a:获取完整路径:
if (fullCheck) { ...
result = newBuffer.GetFullPathName();
GetFullPathName() 调用Win32Native.GetFullPathName
一两次(取决于结果路径的长度)。
1.b. 试图扩大短途。您的路径包含~
字符,因此它看起来像是扩展路径的候选路径:
if (mightBeShortFileName) {
bool r = newBuffer.TryExpandShortFileName();
因此,调用了 Win32Native.GetLongPathName()。
FileIoPermission.Demand() (仅适用于非受信任):
// All demands in full trust domains are no-ops, so skip if (!CodeAccessSecurityEngine.QuickCheckForAllDemands()) { ... new FileIOPermission(secAccess, control, new String[] { filePath }, false, false).Demand();
打开文件流(软盘反击;)):
// Don't pop up a dialog for reading from an emtpy floppy drive int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); try { ... _handle = Win32Native.SafeCreateFile(tempPath, fAccess, share, secAttrs, mode, flagsAndAttributes, IntPtr.Zero);
Win32Native.GetFileType()
并非所有这些都会导致 smb 请求,但有些会导致。我尝试通过使用源逐步调试(这是启用 .net 源调试的手册)并在每个步骤后检查日志来重现聊天请求。Resuts更类似于您的首次上市。如果你真的有兴趣找到真正的问题,你必须自己做。
UPD 请注意,我已经检查了当前 (.net 4.5.2) 行为。自 2.0 以来,它被多次更改(例如 FileIOPermission.Demand()
最初也是为完全受信任的代码调用的),所以这取决于:)
对于为什么 .NET 实现如此健谈,我真的没有具体的答案,但是此行为是由于System.IO.FileStream
的实现,因为File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
所做的只是将参数传递给 FileStream 构造函数。
public static FileStream Open(string path, FileMode mode, FileAccess access, FileShare share)
{
return new FileStream(path, mode, access, share);
}
更改FileStream的行为意味着您基本上必须重新实现FileStream类,这将需要大量的努力。
另一个更简单的替代方法是创建一个本机包装器,该包装器调用您给出的C++代码。然后从 C# 代码调用本机包装器。