Расчет размера файла каталога-как сделать это быстрее?

используя C#, я нахожу общий размер каталога. Логика такова: получить файлы внутри папки. Суммируйте общий размер. Найдите, есть ли подкаталоги. Затем выполните рекурсивный поиск.

я попробовал еще один способ сделать это: используя FSO (obj.GetFolder(path).Size). В обоих этих подходах разница во времени невелика.

теперь проблема в том, что у меня есть десятки тысяч файлов в определенной папке, и это занимает по крайней мере 2 минуты, чтобы найти размер папки. Кроме того, если я снова запустите программу, это происходит очень быстро (5 секунд). Я думаю, что windows кэширует размеры файлов.

есть ли способ уменьшить время, затраченное при первом запуске программы??

7 ответов


Если возился с ним некоторое время, пытаясь распараллелить его, и удивительно - он ускорился здесь на моей машине (до 3 раз на quadcore), не знаю, действительно ли это во всех случаях, но попробуйте...

.NET4.0 код (или используйте 3.5 с TaskParallelLibrary)

    private static long DirSize(string sourceDir, bool recurse)
    {
        long size = 0;
        string[] fileEntries = Directory.GetFiles(sourceDir);

        foreach (string fileName in fileEntries)
        {
            Interlocked.Add(ref size, (new FileInfo(fileName)).Length);
        }

        if (recurse)
        {
            string[] subdirEntries = Directory.GetDirectories(sourceDir);

            Parallel.For<long>(0, subdirEntries.Length, () => 0, (i, loop, subtotal) =>
            {
                if ((File.GetAttributes(subdirEntries[i]) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint)
                {
                    subtotal += DirSize(subdirEntries[i], true);
                    return subtotal;
                }
                return 0;
            },
                (x) => Interlocked.Add(ref size, x)
            );
        }
        return size;
    }

жесткие диски-интересный зверь-последовательный доступ (например, чтение большого смежного файла) - super zippy, рисунок 80megabytes/sec. однако случайный доступ очень медленный. это то, на что вы натыкаетесь - рекурсия в папки не будет читать много (с точки зрения количества) данных, но потребует много случайных чтений. Причина, по которой вы видите zippy perf во второй раз, заключается в том, что MFT все еще находится в ОЗУ (вы правы в мысли о кэшировании)

лучший механизм Я видел, чтобы достичь этого, чтобы сканировать MFT самостоятельно. Идея заключается в том, что Вы читаете и анализируете MFT за один линейный проход, создавая необходимую вам информацию. Конечный результат будет чем-то гораздо ближе к 15 секундам на HD, который очень полон.

хорошей книгой: Программа ntfsinfo.exe -http://technet.microsoft.com/en-us/sysinternals/bb897424.aspx Внутренние Компоненты Windows - http://www.amazon.com/Windows%C2%AE-Internals-Including-Windows-PRO-Developer/dp/0735625301/ref=sr_1_1?ie=UTF8&s=books&qid=1277085832&sr=8-1

FWIW: этот метод очень сложный, поскольку на самом деле нет отличного способа сделать это в Windows (или любой ОС, о которой я знаю) - проблема в том, что акт выяснения, какие папки/файлы необходимы, требует большого движения головы на диске. Для Microsoft было бы очень сложно создать общее решение проблемы, которую вы описывать.


короткий ответ: нет. Способ, которым Windows может сделать вычисление размера каталога более быстрым, - это обновить размер каталога и все размеры родительского каталога для каждой записи файла. Однако это сделает запись файла более медленной операцией. Поскольку гораздо чаще делать записи файлов, чем читать размеры каталогов, это разумный компромисс.

Я не уверен, какая именно проблема решается, но если это мониторинг файловой системы, возможно, стоит проверить: http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx


Я не думаю, что это сильно изменится, но это может пойти немного быстрее, если вы используете функции API FindFirstFile и NextFile сделать это.

Я не думаю, что есть какой-либо действительно быстрый способ сделать это. Для целей сравнения вы можете попробовать сделать dir /a /x /s > dirlist.txt и перечислить каталог в Проводнике Windows, чтобы увидеть, насколько они быстры, но я думаю, что они будут похожи на FindFirstFile.

вызов PInvoke имеет образец того, как использовать API.


Peformance будет страдать используя любой метод при сканировании папки с десятками тысяч файлов.

  • использование FINDFIRSTFILE API Windows... и findnextfile функция... функции обеспечивают самый быстрый доступ.

  • из-за маршалинга накладных расходов, даже если вы используете функции Windows API, производительность не будет увеличиваться. Платформа уже обертывает эти функции API, поэтому нет смысла делать это себе.

  • как вы регулируете результаты для любого метода доступа к файлам определяет производительность вашего приложения. Например, даже если вы используете функции Windows API, обновление списка-это то, где производительность пострадает.

  • нельзя сравнивать скорость в Проводнике Windows. Из моих экспериментов я считаю, что проводник Windows читает непосредственно из таблицы распределения файлов во многих случаи.

  • Я знаю, что самый быстрый доступ к файловой системе-это . Нельзя сравнивать производительность с этой командой. Он определенно читает непосредственно из таблицы распределения файлов (возможно, используя BIOS).

  • Да, операционная система кэширует доступ к файлам.

предложения

  • интересно, если BackupRead поможет кейс?

  • что, если вы раскошелитесь на DIR и захватите, а затем проанализируете его выход? (На самом деле вы не анализируете, потому что каждая строка DIR имеет фиксированную ширину, поэтому это просто вопрос вызова подстроки.)

  • что, если вы раскошелитесь на DIR /B > NULL в фоновом потоке затем запустите свою программу? Во время работы DIR вы получите доступ к кэшированным файлам.


с десятками тысяч файлов, вы не собираетесь выиграть с лобовым нападением. Вам нужно попытаться быть немного более творческим с решением. С таким количеством файлов вы, вероятно, даже найдете, что за время, необходимое для вычисления размера, файлы изменились, и ваши данные уже ошибочны.

Итак, вам нужно переместить нагрузку в другое место. Для меня ответом было бы использовать System.IO.FileSystemWatcher и напишите код, который отслеживает каталог и обновляет индекс.

запись службы Windows, которая может быть настроена для мониторинга набора каталогов и записи результатов в общий выходной файл, займет совсем немного времени. Вы можете заставить службу пересчитать размеры файлов при запуске, но затем просто отслеживать изменения всякий раз, когда событие Create/Delete/Changed запускается System.IO.FileSystemWatcher. Преимущество мониторинга каталога заключается в том, что вас интересуют только небольшие изменения, а это означает, что ваши цифры имеют более высокий шанс быть правильным (помните, что все данные устаревшие!)

тогда единственное, на что нужно обратить внимание, - это то, что у вас будет несколько ресурсов, пытающихся получить доступ к результирующему выходному файлу. Так что просто убедитесь, что вы принимаете это во внимание.


Я отказался от реализаций .NET (по соображениям производительности) и использовал собственную функцию GetFileAttributesEx(...)

попробуйте это:

[StructLayout(LayoutKind.Sequential)]
public struct WIN32_FILE_ATTRIBUTE_DATA
{
    public uint fileAttributes;
    public System.Runtime.InteropServices.ComTypes.FILETIME creationTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME lastAccessTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME lastWriteTime;
    public uint fileSizeHigh;
    public uint fileSizeLow;
}

public enum GET_FILEEX_INFO_LEVELS
{
    GetFileExInfoStandard,
    GetFileExMaxInfoLevel
}

public class NativeMethods {
    [DllImport("KERNEL32.dll", CharSet = CharSet.Auto)]
    public static extern bool GetFileAttributesEx(string path, GET_FILEEX_INFO_LEVELS  level, out WIN32_FILE_ATTRIBUTE_DATA data);

}

теперь просто сделайте следующее:

WIN32_FILE_ATTRIBUTE_DATA data;
if(NativeMethods.GetFileAttributesEx("[your path]", GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out data)) {

     long size = (data.fileSizeHigh << 32) & data.fileSizeLow;
}