Расчет размера файла каталога - как сделать это быстрее? - PullRequest
19 голосов
/ 05 июня 2010

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

Я тоже попробовал другой способ сделать это: Использование FSO (obj.GetFolder(path).Size). В обоих этих подходах нет большой разницы во времени.

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

Можно ли как-нибудь сократить время, необходимое для первого запуска программы?

Ответы [ 8 ]

34 голосов
/ 05 июня 2010

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

.NET4.0 Code (или используйте 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;
    }
10 голосов
/ 21 июня 2010

Жесткие диски - интересный зверь - последовательный доступ (например, чтение большого непрерывного файла) очень быстрый, скорость 80 мегабайт / сек. однако произвольный доступ очень медленный. это то, что вы наталкиваетесь - повторное обращение к папкам не будет читать много (с точки зрения количества) данных, но потребует много случайных чтений. Причина, по которой вы видите быстрый ход второй раз, заключается в том, что 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 было бы очень сложно найти общее решение описываемой вами проблемы.

7 голосов
/ 05 июня 2010

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

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

1 голос
/ 16 июня 2010

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

  • Использование функций Windows API FindFirstFile ... и FindNextFile ... обеспечивает самый быстрый доступ.

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

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

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

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

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

Предложения

  • Интересно, поможет ли BackupRead в вашем случае?

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

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

1 голос
/ 05 июня 2010

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

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

PInvoke содержит пример использования API.

0 голосов
/ 13 ноября 2018

Основываясь на ответе spookycoder, я обнаружил, что этот вариант (с использованием DirectoryInfo) как минимум в 2 раза быстрее (и до 10 раз быстрее для сложных структур папок!):

    public static long CalcDirSize(string sourceDir, bool recurse = true)
    {
        return _CalcDirSize(new DirectoryInfo(sourceDir), recurse);
    }

    private static long _CalcDirSize(DirectoryInfo di, bool recurse = true)
    {
        long size = 0;
        FileInfo[] fiEntries = di.GetFiles();
        foreach (var fiEntry in fiEntries)
        {
            Interlocked.Add(ref size, fiEntry.Length);
        }

        if (recurse)
        {
            DirectoryInfo[] diEntries = di.GetDirectories("*.*", SearchOption.TopDirectoryOnly);
            System.Threading.Tasks.Parallel.For<long>(0, diEntries.Length, () => 0, (i, loop, subtotal) =>
            {
                if ((diEntries[i].Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) return 0;
                subtotal += __CalcDirSize(diEntries[i], true);
                return subtotal;
            },
                (x) => Interlocked.Add(ref size, x)
            );

        }
        return size;
    }
0 голосов
/ 21 июня 2010

Я отказался от реализации .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;
}
0 голосов
/ 17 июня 2010

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

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

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

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

...