![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Несколько ускоренный способ сканирования директории.
Riply © (2007-10-14 21:09:00)Здравствуйте !
Т.к. последнее время эта тема стала популярной на форуме, решила
попробовать посмотреть на нее с точки зрения Naitive API.
(Если честно, то мне самой это понадобилось для сравнительного анализа
и контроля моих MFT-творений и изучения "как это делают другие" :)
Почитала, что по этому поводу говорят в интернете.
Во всех местах (которые я сумела найти) приходят к одному и тому-же
выводу: "Этим путем - не ускориться".
Давайте попробуем усомниться в этом ? :)
(Усомняться мы будем, подглядывая в исходники Игоря Шевченко :)
Сначала определим структуру в которой будем хранить данные
для каждого объекта. Она должна быть очень маленькой.
Ведь если мы сканируем диск с миллионом объектов, то увеличение ее размера
всего на один байт съест целый мегабайт памяти.
OBJ_DATA_INF0 = record
case Integer of
0: (ChildCount: DWORD;
FirstChildOffset: DWORD); // для директорий
1: (SizeOfData: LONGLONG); // для файлов и файловых потоков
end;
_NAMED_QUERY_OBJECT = packed record
OffsetNextEntry : Word; // Смещение на следующий блок
ObjAttributes : ULONG; // Атрибуты объекта
ExternalData : OBJ_DATA_INF0;
ObjNameLength : Word; // нилл-терминайт не считаем.
ObjName : array[0..0] of Byte;
end;
(Здесь оставлен простор для уменьшения :)
Например, в ExternalData.SizeOfData предполагается хранить размер файла.
Но для него вполне хватит и 48 бит. Так что туда можно запихать ObjNameLength
и сэкономить аж целых два байта :) Так же можно уменьшить и ObjAttributes,
переопределив, какие и сколько нам надо.
"Оставляю это заинтересованным лицам" (с) Leonid Troyanovsky
И заодно пара функций для расчета размера записи и выравнивания его до SizeOf(DWord)
(Мудрые люди из Microsoft выравнивают структуру FILE_BOTH_DIR_INFORMATION
до SizeOf(Int64), ну а мы (т.к. плохо понимаем "зачем и почему") остановимся на DWord :)
function NT_NameAddedSizeB(const NameLength: integer): integer; inline;
begin
if NameLength >= SizeOf(WideChar)
then Result := NameLength - SizeOf(Byte) else Result := -1;
end;
function QueryObj_EdgeSize(const BytesInName: integer): integer;
var
Value: integer;
begin
Value := SizeOf(_NAMED_QUERY_OBJECT) + NT_NameAddedSizeB(BytesInName);
Result := Value mod SizeOf(DWord);
if Result > 0 then Result := Value + SizeOf(DWord) - Result else inc(Result, Value);
end;
Ну а теперь можно и посканировать. Я приведу только схему кода,
оговорив параметры вызовов, т.к. кроме этого и обычной рекурсии в нем ничего нет :)
Используемые функции: NtQueryDirectoryFile, NtOpenFile и NtClose.
Нам понадобяться два блока памяти: для буффера, передаваемого в NtQueryDirectoryFile
и для списка наших NAMED_QUERY_OBJECT.
Попробуем оценить их размеры:
NtQueryDirectoryFile будем вызывать так:
ObjectAttr: POBJECT_ATTRIBUTES - инициализируем хендлом открытого родителя
(он у нас будет, мы же в рекурсии) и только именем файла (без пути).
NtQueryDirectoryFile(..., @ObjectAttr(POBJECT_ATTRIBUTES),...
FileDirectoryInformation(FILE_INFORMATION_CLASS), - выбор класса важен.
False(ReturnSingleEntry),...);
Мы за один вызов будем пытаться получить все объекты (ReturnSingleEntry = False),
и экономить на размере буфера не будем, т.к. его нехватка потребует не только
перевыделения памяти, но и лишнего вызова NtQueryDirectoryFile.
Такого размера: FS_OBJ_QUERY_INC_SIZE = $10000 * 8 хватает, примерно на 5000 объектов,
лежащих в корне сканируемого.
Примерная работа:
function TNtQueryObjectFS._QueryDirectoryFileMem(const ObjHandle: THandle;
const InfoClass: FILE_INFORMATION_CLASS;
RetSngleEntry, RestartScan: LongBool): NTSTATUS;
var
IoBlock: TIoStatusBlock;
begin
Result := NtQueryDirectoryFile(ObjHandle, FhEvent, nil, nil, @IoBlock, FpObjInfo,
FcbObjInfo, InfoClass, RetSngleEntry, nil, RestartScan);
while True do
case Result of
STATUS_BUFFER_OVERFLOW, STATUS_INFO_LENGTH_MISMATCH:
begin
_ReallocQueryBuffer(FcbObjInfo + FcbObjInfo);
Result := NtQueryDirectoryFile(ObjHandle, FhEvent, nil, nil, @IoBlock, FpObjInfo,
FcbObjInfo, InfoClass, RetSngleEntry, nil,
Result = STATUS_INFO_LENGTH_MISMATCH);
end;
else Exit;
end;
end;
function TNtQueryObjectFS._QueryDirectoryFile(const aOffset: DWord; const ObjHandle: THandle;
const InfoClass: FILE_INFORMATION_CLASS;
RetSngleEntry: LongBool): NTSTATUS;
begin
Result := _QueryDirectoryFileMem(ObjHandle, InfoClass, RetSngleEntry, True);
while Result = STATUS_SUCCESS do
begin
FROOT_FIND_DATA.AddObjectsBlock(aOffset, FpObjInfo, InfoClass, FirstCall);<--Пишем наши данные
Result := _QueryDirectoryFileMem(ObjHandle, InfoClass, RetSngleEntry, False);
end;
end;
Riply (2007-10-14 21:10:00) [21]Примерно так выглядит тело рекурсии:
function TNtQueryObjectFS._QueryFileDirectoryRec(const aOffset: DWord; const NtScanRec: TNtScanRec;
const DesAccess: ACCESS_MASK;
const InfoClass: FILE_INFORMATION_CLASS;
const RetSngle: Boolean) : NTSTATUS;
var
ObjectAttr: OBJECT_ATTRIBUTES;
IoBlock: TIoStatusBlock;
usSubObj: TUnicodeString;
_ScanRec: TNtScanRec;
Tmp, Offset, LastOffset: DWord;
begin
Tmp := NtScanRec.RootNameLen shr SHR_WCHAR;
InitializeObjectAttributes(@ObjectAttr, @NtScanRec.ObjName, OBJ_CASE_INSENSITIVE, NtScanRec.hParent, nil);
Result := NtOpenFile(@_ScanRec.hParent, DesAccess, @ObjectAttr, @IoBlock, ALX_FILE_SHARE_ALL,
FILE_SYNCHRONOUS_IO_NONALERT or FILE_DIRECTORY_FILE);
if FixStatus(Result) = STATUS_SUCCESS then
try
Us_InitNull(@_ScanRec.ObjName);
Offset := FROOT_FIND_DATA.UsedSize;
Result := _QueryDirectoryFile(aOffset, _ScanRec.hParent, InfoClass, RetSngle);
if Result = STATUS_NO_MORE_FILES then
begin
LastOffset := FROOT_FIND_DATA.UsedSize;
while Offset < LastOffset do
begin
with FROOT_FIND_DATA.Blocks(Offset)^ do
begin
inc(FMemCount, QueryObj_EdgeSize(Tmp + SizeOf(Char) + (ObjNameLength shr SHR_WCHAR)));
if IsNotScannedDirectory then
if GetNameU(@usSubObj) = STATUS_SUCCESS then
begin
Result := Us_Duplicate(@usSubObj, @_ScanRec.ObjName);
if Result = STATUS_SUCCESS then
try
_ScanRec.RootNameLen := _ScanRec.ObjName.Length + SizeOf(WideChar) + NtScanRec.RootNameLen;
_QueryFileDirectoryRec(Offset, _ScanRec, DesAccess, InfoClass, RetSngle);
finally
Us_FreeMem(@_ScanRec.ObjName);
end;
end;
inc(Offset, FROOT_FIND_DATA.Blocks(Offset).OffsetNextEntry);
end;
end;
end;
finally
Us_FreeMem(@_ScanRec.ObjName);
Nt_Close(_ScanRec.hParent);
end
else Err_ShowNT(Result, usSubObj.Buffer);
end;
В этой ф-ии мы записываем полученные результаты в виде:
имени(без пути)
смещения на следующий элемент
смещения на первого ребенка
атрибутов.
Заодно высчитываем точный размер будующей структуры в которую
конвертируем все это дело для удобства последующей работы с данными :)
После пояснений Игоря Шевченко и clickmaker`а о механизме выделения памяти в
Rtl ф-иях для UNICODE_STRING, я (в данной задаче) использовала "самописные"
ф-ии - аналоги Rtl ф-ий,только использующие GetMem, FreeMem.
(Мизерный выигрыш - а приятно :)
При оценке начального размера буфера для NAMED_QUERY_OBJECT, я поступаю так:
Если нас вызвали просканировать целый диск, то считываю значение MftValidDataLength из MFT.
(все равно нам открывать диск :) и поделив его на размер записи в MFT получаю
довольно точное число валидных объектов на диске.
Если же сканировать надо директорию, то начальный размер берем с потолка :)
Резюме:
Мы пытались выиграть на:
1. Уходе от постоянной ковертации больших строк из PChar в UNICODE_STRING и обратно.
2. Открытии всех объектов, используя Handle родителя.
3. Получении сразу всех "корневых" объектов за один запрос.
4. Хранении данных "в одном блоке памяти"
Сравнивалось заполнение TStrings при помощи FindFirst/NextFile, с одной стороны
и полный объем работ (подготовка, сканирование, конвертация результатов в
индексированную структуру, и последующее заполнение из нее TStrings) с другой стороны.
Тестирование показало выигрыш в скорости
на "индексированном" диске - в два раза
на не "индексированном" диске - на 20-25%.
Учитывая, что все это нуждается в доработке напильником, оптимизации и исправлении ошибок,
результат очень даже ничего :)
P.S.
Доводить это хозяйство "до ума" не буду, ибо данная методика
не идет ни в какое сравнение с работой через MFT, а любопытство свое я уже удовлетворила :)
P.P.S.
Уфф... :)