У меня есть блок кода, который должен открывать и читать множество небольших текстовых файлов с сервера NAS, используя пути UNC. Этот код является частью модуля, который был изначально написан на 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();
Открыть файлStream (дискета наносит ответный удар;)):
// 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);
Не все из них приведут к запросу кого-то, но некоторые сделают. Я пытался воспроизвести болтливые запросы путем пошаговой отладки с исходным кодом (вот руководство по включению отладки исходного кода .net) и проверке журнала после каждого шага. Результаты больше похожи на ваш первый список.
Если вы действительно заинтересованы в поиске реальной проблемы, вам придется сделать это самостоятельно.
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 #.