Перечислять каталоги в Parallel потребляет очень много физической памяти - PullRequest
0 голосов
/ 31 августа 2018

Я написал утилиту, которая будет искать все фиксированные диски в системе для файлов определенного расширения. Некоторые из дисков содержат миллионы папок (скажем, 30 миллионов, например), и файлы могут быть найдены на разной глубине (скажем, 6/7 подпапка). Найдите под функцией, которую я использую,

private void ReadDirectories(string targetDirectory)
    {
        IEnumerable<string> files = Directory.EnumerateFiles(targetDirectory).AsParallel();
        ConcurrentBag<string> filesBag = new ConcurrentBag<string>(files);
        Parallel.ForEach(filesBag, (file) =>
       {
           Interlocked.Increment(ref totalFileCount);
           if (extension is a text/excel/word file )
           {
               try
               {
                   // Some logic here
               }
               catch (AggregateException Aex)
               {
                   Log("Aggregate exception thrown. " + Aex.Message + Aex.StackTrace + Aex.InnerException);
               }
               catch (Exception ex)
               {
                   Log("File read failed: " + file + ex.Message + ex.StackTrace + ex.InnerException);
                   return; // This is break equivalent in Parallel.ForEach

               }
           }

       });

        IEnumerable<string> directories = Directory.EnumerateDirectories(targetDirectory).AsParallel();
        ConcurrentBag<string> directoryBag = new ConcurrentBag<string>(directories);
        Parallel.ForEach(directoryBag, (subDirectory) =>
         {
             try
             {
                 ReadDirectories(subDirectory);
             }
             catch (AggregateException Aex)
             {
                 Log("Aggregate exception thrown. " + Aex.Message + Aex.StackTrace + Aex.InnerException);
             }
             catch (UnauthorizedAccessException Uaex)
             {
                 Log("Unauthorized exception: " + Uaex.Message + Uaex.StackTrace + Uaex.InnerException);
                 return;
             }
             catch (AccessViolationException Aex)
             {
                 Log("Access violation exception: " + Aex.Message + Aex.StackTrace + Aex.InnerException);
                 return;
             }
             catch (Exception ex)
             {
                 Log("Error while reading directories and files : " + ex.Message + ex.StackTrace + ex.InnerException);
                 return;
             }
         });

    }

Проблема, с которой я сталкиваюсь, заключается в том, что как только приложение начинает перечислять папки, физическая память все больше и больше расходуется и через некоторое время достигает своего пика (99%). На данный момент никакие другие действия не могут быть выполнены. Но объем памяти моего приложения составляет около 80 -90 МБ. Хотите узнать причину, по которой физическое использование памяти так высоко, что-то не так с кодом?

Ответы [ 2 ]

0 голосов
/ 03 сентября 2018

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

Быстрее использовать Directory.EnumerateFiles или даже лучше, DirectoryInfo.EnumerateFiles с SearchOption.AllDirectories для перечисления всех файлов в текущей папке и подпапках и обрабатывать файлы параллельно.

Быстрый и грязный вариант - использовать запрос LINQ для фильтрации всех целевых файлов и Parallel.ForEach для обработки файлов, например:

var extensions=new[]{".docx", ".xlsx",...};
var folder=new DirectoryInfo(targetDirectory);
var files=from file in folder.EnumerateFiles("*.*", SearchOption.AllDirectories)
          where extensions.Contains(file.Extension,StringComparer.InvariantCultureIgnoreCase)
          select file;

Parallel.ForEach(files,file=>ProcessFile(file));

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

var options=new ParallelOptions { MaxDegreeOfParallelism = 4 }
Parallel.ForEach(files,options,ProcessFile);

Parallel.ForEach будет извлекать имена файлов из запроса files по мере необходимости. Он начнет обработку, как только EnumerateFiles вернет первые результаты, вместо того, чтобы ожидать загрузки всех имен файлов и их кэширования в памяти.

0 голосов
/ 31 августа 2018

Рассмотрим ваши цифры: 30 миллионов папок, каждая из которых, вероятно, содержит несколько файлов, оставляет вам примерно 100 миллионов строк для имен файлов и каталогов. Из-за того, что метод рекурсивный, все мешки хранятся до конца рекурсии.

Таким образом, при средней длине имени файла / каталога в 100 символов вы получаете до 10 ГБ ОЗУ только для имен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...