为啥在 VB.NET 中使用 DeviceIoControl 进行文件枚举比在 C++ 中更快?
Posted
技术标签:
【中文标题】为啥在 VB.NET 中使用 DeviceIoControl 进行文件枚举比在 C++ 中更快?【英文标题】:Why file enumeration using DeviceIoControl is faster in VB.NET than in C++?为什么在 VB.NET 中使用 DeviceIoControl 进行文件枚举比在 C++ 中更快? 【发布时间】:2014-12-10 05:37:27 【问题描述】:我正在尝试读取 Windows 主文件表 (MFT) 以快速枚举文件。到目前为止,我已经看到了两种方法:
-
正如Jeffrey Cooperstein and Jeffrey Richter 使用DeviceIoControl 所建议的那样
在某些开源工具和An NTFS Parser Lib 中提供的 MFT 的直接解析
对于我的项目,我专注于方法 [1]。我面临的问题主要与执行时间有关。为了清楚起见,以下是我的系统和开发环境:
-
IDE - Visual Studio 2013
语言 - C++
操作系统 - Windows 7 Professional x64
为 C++ 和 .NET 代码生成 32 位二进制文件。
问题
我将 [1] 中提到的版本(稍作修改)与 VB.NET 实现 available on codeplex 进行了比较。问题是如果我取消注释 Inner Loop 中的语句,C++ 代码执行时间会增加 7-8 倍。我还没有在 C++ 代码中实现路径匹配(在 VB 代码中可用)。
Q1。请建议如何提高 C++ 代码的性能。
Timings 用于枚举我机器上的 C:\ 驱动器:
-
C++(在内循环中带有未注释的语句)-
21 seconds
VB.NET(带有附加路径匹配代码)-3.5 seconds
为了更清楚,下面是 C++ 和 VB.NET sn-ps。
C++
bool FindAll()
if (m_hDrive == NULL) // Handle of, for example, "\\.\C:"
return false;
USN_JOURNAL_DATA ujd = 0;
DWORD cb = 0;
BOOL bRet = FALSE;
MFT_ENUM_DATA med = 0;
BYTE pData[sizeof(DWORDLONG) + 0x10000] = 0;
bRet = DeviceIoControl(m_hDrive, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &ujd, sizeof(USN_JOURNAL_DATA), &cb, NULL);
if (bRet == FALSE) return false;
med.StartFileReferenceNumber = 0;
med.LowUsn = 0;
med.HighUsn = ujd.NextUsn;
//Outer Loop
while (TRUE)
bRet = DeviceIoControl(m_hDrive, FSCTL_ENUM_USN_DATA, &med, sizeof(med), pData, sizeof(pData), &cb, NULL);
if (bRet == FALSE)
break;
PUSN_RECORD pRecord = (PUSN_RECORD)&pData[sizeof(USN)];
//Inner Loop
while ((PBYTE)pRecord < (pData + cb))
tstring sz((LPCWSTR) ((PBYTE)pRecord + pRecord->FileNameOffset), pRecord->FileNameLength / sizeof(WCHAR));
bool isFile = ((pRecord->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY);
if (isFile) m_dwFiles++;
//m_nodes[pRecord->FileReferenceNumber] = new CNode(pRecord->ParentFileReferenceNumber, sz, isFile);
pRecord = (PUSN_RECORD)((PBYTE)pRecord + pRecord->RecordLength);
med.StartFileReferenceNumber = *(DWORDLONG *)pData;
return true;
其中m_nodes
定义为typedef std::map<DWORDLONG, CNode*> NodeMap;
VB.NET
Public Sub FindAllFiles(ByVal szDriveLetter As String, fFileFound As FileFound_Delegate, fProgress As Progress_Delegate, fMatch As IsMatch_Delegate)
Dim usnRecord As USN_RECORD
Dim mft As MFT_ENUM_DATA
Dim dwRetBytes As Integer
Dim cb As Integer
Dim dicFRNLookup As New Dictionary(Of Long, FSNode)
Dim bIsFile As Boolean
' This shouldn't be called more than once.
If m_Buffer.ToInt32 <> 0 Then
Console.WriteLine("invalid buffer")
Exit Sub
End If
' progress
If Not IsNothing(fProgress) Then fProgress.Invoke("Building file list.")
' Assign buffer size
m_BufferSize = 65536 '64KB
' Allocate a buffer to use for reading records.
m_Buffer = Marshal.AllocHGlobal(m_BufferSize)
' correct path
szDriveLetter = szDriveLetter.TrimEnd("\"c)
' Open the volume handle
m_hCJ = OpenVolume(szDriveLetter)
' Check if the volume handle is valid.
If m_hCJ = INVALID_HANDLE_VALUE Then
Console.WriteLine("Couldn't open handle to the volume.")
Cleanup()
Exit Sub
End If
mft.StartFileReferenceNumber = 0
mft.LowUsn = 0
mft.HighUsn = Long.MaxValue
Do
If DeviceIoControl(m_hCJ, FSCTL_ENUM_USN_DATA, mft, Marshal.SizeOf(mft), m_Buffer, m_BufferSize, dwRetBytes, IntPtr.Zero) Then
cb = dwRetBytes
' Pointer to the first record
Dim pUsnRecord As New IntPtr(m_Buffer.ToInt32() + 8)
While (dwRetBytes > 8)
' Copy pointer to USN_RECORD structure.
usnRecord = Marshal.PtrToStructure(pUsnRecord, usnRecord.GetType)
' The filename within the USN_RECORD.
Dim FileName As String = Marshal.PtrToStringUni(New IntPtr(pUsnRecord.ToInt32() + usnRecord.FileNameOffset), usnRecord.FileNameLength / 2)
'If Not FileName.StartsWith("$") Then
' use a delegate to determine if this file even matches our criteria
Dim bIsMatch As Boolean = True
If Not IsNothing(fMatch) Then fMatch.Invoke(FileName, usnRecord.FileAttributes, bIsMatch)
If bIsMatch Then
bIsFile = Not usnRecord.FileAttributes.HasFlag(FileAttribute.Directory)
dicFRNLookup.Add(usnRecord.FileReferenceNumber, New FSNode(usnRecord.FileReferenceNumber, usnRecord.ParentFileReferenceNumber, FileName, bIsFile))
End If
'End If
' Pointer to the next record in the buffer.
pUsnRecord = New IntPtr(pUsnRecord.ToInt32() + usnRecord.RecordLength)
dwRetBytes -= usnRecord.RecordLength
End While
' The first 8 bytes is always the start of the next USN.
mft.StartFileReferenceNumber = Marshal.ReadInt64(m_Buffer, 0)
Else
Exit Do
End If
Loop Until cb <= 8
If Not IsNothing(fProgress) Then fProgress.Invoke("Parsing file names.")
' Resolve all paths for Files
For Each oFSNode As FSNode In dicFRNLookup.Values.Where(Function(o) o.IsFile)
Dim sFullPath As String = oFSNode.FileName
Dim oParentFSNode As FSNode = oFSNode
While dicFRNLookup.TryGetValue(oParentFSNode.ParentFRN, oParentFSNode)
sFullPath = String.Concat(oParentFSNode.FileName, "\", sFullPath)
End While
sFullPath = String.Concat(szDriveLetter, "\", sFullPath)
If Not IsNothing(fFileFound) Then fFileFound.Invoke(sFullPath, 0)
Next
'// cleanup
Cleanup() '//Closes all the handles
If Not IsNothing(fProgress) Then fProgress.Invoke("Complete.")
End Sub
其中fFileFound
定义如下:
Sub(s, l)
If s.ToLower.StartsWith(sSearchPath) Then
lCount += 1
lstFileNames.Add(s.ToLower) '// Dim lstFileNames As List(Of String)
End If
End Sub
其中FSNode
& CNode
的结构如下:
//C++ version
class CNode
public:
//DWORDLONG m_dwFRN;
DWORDLONG m_dwParentFRN;
tstring m_sFileName;
bool m_bIsFile;
public:
CNode(DWORDLONG dwParentFRN, tstring sFileName, bool bIsFile = false) :
m_dwParentFRN(dwParentFRN), m_sFileName(sFileName), m_bIsFile(bIsFile)
~CNode()
;
注意 - VB.NET 代码生成了一个新线程(因为它有 GUI,所以需要),而我在主线程中调用 c++ 函数(一个用于测试的简单控制台应用程序)。
更新
在我看来这是一个愚蠢的错误。 DeviceIoControl
API 按预期工作。虽然Debug
构建比Release
构建慢一点。参考以下文章:
how-can-i-increase-the-performance-in-a-map-lookup-with-key-type-stdstring
【问题讨论】:
您能否提供简化程序,仅将调用与DeviceIoControl
进行比较。当您这样做时,您可能会发现 C++ 代码更快,因为它避免了 p/invoke 层。
@David Heffernan - 谢谢。从我这边看,这是一个愚蠢的错误。我在我的项目中使用 C++ STL 容器类,它们在DEBUG
模式下往往运行缓慢。测试 Release
构建后,性能符合预期。
使用sample(你在CODEPLEX中提到的),能否获取createdDate和ModiifedDate?
很好的问题,具有不同可能方法的背景等。我不明白为什么不赞成...
@Favonius 你能发布你关于解析 MFT / NTFS 速度的最新成就/想法吗?会超级有趣的!
【参考方案1】:
我没有运行你的代码,但既然你说注释行是问题,问题可能是地图插入。 在 C++ 代码中,您使用的是 std::map,它被实现为树(按键排序,log(n) 访问时间)。 在 VB 代码中,您使用的是字典,它被实现为哈希表(无排序,访问时间恒定)。 尝试在 C++ 版本中使用 std::unordered_map。
【讨论】:
谢谢。请看我的更新。即使是简单的 tstring 对象创建也会减少Debug
构建的时间。其中tstring
--> typedef std::basic_string<TCHAR> tstring;
以上是关于为啥在 VB.NET 中使用 DeviceIoControl 进行文件枚举比在 C++ 中更快?的主要内容,如果未能解决你的问题,请参考以下文章
为啥代码隐藏文件在 VB.NET Web 应用程序项目中不可见?
为啥将 VB.NET 代码迁移到 C# 时,for 循环的行为会有所不同?