У меня есть ~ 500К файлов * .ax5, которые я должен обработать и экспортировать в другой формат. Из-за большого количества файлов и из-за проблем с производительностью Windows, когда в одной папке находится слишком много файлов, они похоронены во вложенных папках с другими файлами с разными расширениями. Каков самый быстрый способ найти в C # все файлы, содержащиеся в подпапках любого уровня, например, в C: \ Sketch?

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

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

Я не могу изменить исходную структуру файлов / папок, установленную поставщиком

Вот что у меня есть. Я пробовал и Array.ForEach, и Parallel.ForEach, оба кажутся очень медленными.

Sub walkTree(ByVal directory As DirectoryInfo, ByVal pattern As String)
    Array.ForEach(directory.EnumerateFiles(pattern).ToArray(), Sub(fileInfo)
                                                                   Export(fileInfo)
                                                               End Sub)
    For Each subDir In directory.EnumerateDirectories()
        walkTree(subDir, pattern)    
    Next
End Sub
6
Doug Chamberlain 22 Янв 2014 в 19:20

3 ответа

Лучший ответ

http://msdn.microsoft.com/en-us/library/ms143316(v=vs.110).aspx

Directory.GetFiles(@"C:\Sketch", "*.ax5", SearchOption.AllDirectories);

Может быть достаточно для вас?


Что касается производительности, я сомневаюсь, что вы найдете гораздо более быстрые способы сканирования каталогов, поскольку, как указывает @Mathew Foscarini, ваши диски являются узким местом здесь.

Если каталог проиндексирован, было бы быстрее использовать это, как упоминает @jaccus.


Мне потребовалось время, чтобы немного протестировать ситуацию. И действительно кажется, что вы можете получить прирост производительности примерно на 33% при асинхронном сборе файлов.

Тестовый набор, на котором я работал, может не соответствовать вашей ситуации, я не знаю, насколько вложены ваши файлы и т. Д ... Но я создал 5000 случайных файлов в каждом каталоге на каждом уровне (я остановился на одном уровне, хотя ) и 100 каталогов на 505000 файлов ...

Протестировал 3 метода сбора файлов ...

Самый простой подход.

public class SimpleFileCollector
{
    public List<string> CollectFiles(DirectoryInfo directory, string pattern)
    {
        return new List<string>( Directory.GetFiles(directory.FullName, pattern, SearchOption.AllDirectories));
    }
}

«Тупой» подход, хотя это глупо только в том случае, если вы знаете о перегрузке, используемой в простом подходе ... В противном случае это прекрасное решение.

public class DumbFileCollector
{
    public List<string> CollectFiles(DirectoryInfo directory, string pattern)
    {
        List<string> files = new List<string>(500000);
        files.AddRange(directory.GetFiles(pattern).Select(file => file.FullName));

        foreach (DirectoryInfo dir in directory.GetDirectories())
        {
            files.AddRange(CollectFiles(dir, pattern));
        }
        return files;
    }
}

Подход к API задач ...

public class ThreadedFileCollector
{
    public List<string> CollectFiles(DirectoryInfo directory, string pattern)
    {
        ConcurrentQueue<string> queue = new ConcurrentQueue<string>();
        InternalCollectFiles(directory, pattern, queue);
        return queue.AsEnumerable().ToList();
    }

    private void InternalCollectFiles(DirectoryInfo directory, string pattern, ConcurrentQueue<string> queue)
    {
        foreach (string result in directory.GetFiles(pattern).Select(file => file.FullName))
        {
            queue.Enqueue(result);
        }

        Task.WaitAll(directory
            .GetDirectories()
            .Select(dir => Task.Factory.StartNew(() => InternalCollectFiles(dir, pattern, queue))).ToArray());
    }
}

Это всего лишь тест на сбор всех файлов. Не обрабатывая их, имело бы смысл перейти к потокам.

Вот результаты в моей системе:

Simple Collector:
 - Pass 0: found 505000 files in 2847 ms
 - Pass 1: found 505000 files in 2865 ms
 - Pass 2: found 505000 files in 2860 ms
 - Pass 3: found 505000 files in 3061 ms
 - Pass 4: found 505000 files in 3006 ms
 - Pass 5: found 505000 files in 2807 ms
 - Pass 6: found 505000 files in 2849 ms
 - Pass 7: found 505000 files in 2789 ms
 - Pass 8: found 505000 files in 2790 ms
 - Pass 9: found 505000 files in 2788 ms
Average: 2866 ms

Dumb Collector:
 - Pass 0: found 505000 files in 5190 ms
 - Pass 1: found 505000 files in 5204 ms
 - Pass 2: found 505000 files in 5453 ms
 - Pass 3: found 505000 files in 5311 ms
 - Pass 4: found 505000 files in 5339 ms
 - Pass 5: found 505000 files in 5362 ms
 - Pass 6: found 505000 files in 5316 ms
 - Pass 7: found 505000 files in 5319 ms
 - Pass 8: found 505000 files in 5583 ms
 - Pass 9: found 505000 files in 5197 ms
Average: 5327 ms

Threaded Collector:
 - Pass 0: found 505000 files in 2152 ms
 - Pass 1: found 505000 files in 2102 ms
 - Pass 2: found 505000 files in 2022 ms
 - Pass 3: found 505000 files in 2030 ms
 - Pass 4: found 505000 files in 2075 ms
 - Pass 5: found 505000 files in 2120 ms
 - Pass 6: found 505000 files in 2030 ms
 - Pass 7: found 505000 files in 1980 ms
 - Pass 8: found 505000 files in 1993 ms
 - Pass 9: found 505000 files in 2120 ms
Average: 2062 ms

В качестве побочного примечания @Konrad Kokosa предложил заблокировать каждый каталог, чтобы не запускать миллионы потоков, не делайте этого ...

У вас нет причин управлять количеством потоков, которые будут активны в данный момент времени, пусть стандартный планировщик Task frameworks справится с этим, он будет выполнять гораздо лучшую работу по балансировке количества потоков в зависимости от количества ядер, которые у вас есть. ...

И если вы действительно не хотите контролировать это самостоятельно только потому, что внедрение настраиваемого планировщика было бы лучшим вариантом: http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler (v = vs.110). aspx

8
Jens 23 Янв 2014 в 08:28

В общем, параллелизм в сценарии простого поиска, вероятно, приведет только к дополнительным накладным расходам. Но если Export почему-то стоит дорого, вы, вероятно, сможете получить некоторые преимущества в производительности с помощью многопоточности. Вот код, создающий многопоточную версию на C # и VB.NET (протестирован):

public static async Task<IEnumerable<string>> ProcessDirectoryAsync(string path, string searchPattern)
{
    var files = Directory.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
    var subdirs = Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly);
    var results = await Task.WhenAll(files.Select(f => Task.Run(() => ExportFile(f))));
    var subresults = await Task.WhenAll(subdirs.Select(dir => Task.Run(() => ProcessDirectoryAsync(dir, searchPattern))));
    return results.Concat(subresults.SelectMany(r => r));
}

Public Shared Async Function ProcessDirectoryAsync(path As String, searchPattern As String) As Task(Of IEnumerable(Of String))
    Dim source As IEnumerable(Of String) = Directory.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly)
    Dim source2 As IEnumerable(Of String) = Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)
    Dim first As String() = Await Task.WhenAll(Of String)(
        source.Select(Function(f As String) Task.Run(Of String)(
                          Function() ExportFile(f))
                      ))
    Dim source3 As IEnumerable(Of String)() =
        Await Task.WhenAll(Of IEnumerable(Of String))(
            source2.Select(Function(dir As String) _
                               Task.Run(Of IEnumerable(Of String))(
                                   Function() ProcessDirectoryAsync(dir, searchPattern)
                               )))
    Return first.Concat(source3.SelectMany(Function(r As IEnumerable(Of String)) r))
End Function

Ожидание не позволяет этому коду порождать миллионы потоков для каждого каталога / файла. Быстрые тесты, которые я провел, показывают, что до 5-6 рабочих потоков выполняют свою работу. Прирост производительности может быть примерно в 4 раза.

0
Konrad Kokosa 22 Янв 2014 в 16:15

Вы можете попробовать Windows Search API или API Службы индексирования.

1
jaccus 22 Янв 2014 в 15:26