【问题标题】:Parallel Loops for Folder Size Determination用于确定文件夹大小的并行循环
【发布时间】:2016-07-12 17:51:02
【问题描述】:

整个程序的目标是确定目录中主文件夹的大小。它适用于小型驱动器,但适用于较大的驱动器。我绝对需要的驱动器之一花了 3 个多小时。这是我正在使用的文件夹大小调整程序的副本。

    public  double getDirectorySize(string p)
    {

        //get array of all file names
        string[] a = Directory.GetFiles(p, "*.*", SearchOption.AllDirectories);

        //calculate total bytes in loop
        double b = 0;
        foreach (string name in a)
        {

            if (name.Length < 250) // prevents path too long errors
            {


                    //use file info to get length of each file 
                    FileInfo info = new FileInfo(name);
                    b += info.Length;
            }
        }

        //return total size
        return b;
    }

所以我想以并行 foreach 循环的形式使用并行循环。每个 p 代表主文件夹的名称。我正在考虑以某种方式将路径 p 拆分为其子文件夹并使用并行 foreach 循环继续收集文件大小;但是,它们具有未知数量的子目录。这是我在尝试恢复文件夹大小时遇到​​问题的地方。提前感谢您的帮助

更新

我通过下面这个foreach循环调用这个函数

           DirectoryInfo di = new DirectoryInfo    (Browse_Folders_Text_Box.Text);
            FileInfo[] parsedfilename = di.GetFiles("*.*", System.IO.SearchOption.TopDirectoryOnly);
            parsedfoldername = System.IO.Directory.GetDirectories(Browse_Folders_Text_Box.Text, "*.*", System.IO.SearchOption.TopDirectoryOnly);
            //parsedfilename = System.IO.Directory.GetDirectories(textBox1.Text, "*.*", System.IO.SearchOption.AllDirectories);





            // Process the list of folders found in the directory.

            type_label.Text = "Folder Names \n";


            List<string> NameList = new List<string>();
            foreach (string transfer2 in parsedfoldername)
            {

                this.Cursor = Cursors.WaitCursor;
                //Uses the path and takes the name from last folder used
                string dirName = new DirectoryInfo(@transfer2).Name;
                string dirDate = new DirectoryInfo(@transfer2).LastWriteTime.ToString();


                NameList.Add(dirName);
                //Form2 TextTable = new Form2(NameList.ToString());



                //Display_Rich_Text_Box.AppendText(dirName);
                //Display_Rich_Text_Box.AppendText("\n");
                Last_Date_Modified_Text_Box.AppendText(dirDate);
                Last_Date_Modified_Text_Box.AppendText("\n");


                try
                {
                    double b;

                    b = getDirectorySize(transfer2);
                    MetricByte(b);



                }
                catch (Exception)
                {
                    Size_Text_Box.AppendText("N/A \n");                      
                }

            }

            Display_Rich_Text_Box.Text = string.Join(Environment.NewLine, NameList);
            this.Cursor = Cursors.Default;

所以当我想到并行 foreach 循环时,我在想的是获取下一个实例名称(子文件夹名称),它们都在同一级别,并使用 getDirectorySize() 同时运行它们,因为我知道那里在主文件夹名称的正下方至少有 7 个子文件夹。

【问题讨论】:

  • 您是否想过使用递归搜索而不是使用Directory.GetFiles 一次性获取整个驱动器上的所有文件?我认为它在内存方面可能比加载可能包含数百万个条目的巨大结果数组更有效。
  • Directory.GetFiles 正在获取仅 p 的文件,这是一个文件夹。我在使用 foreach 语句获取驱动器中的主文件夹名称后调用此函数。我考虑了一个递归函数,但通常它们是 void 函数,我仍然希望返回 b (主文件夹的实际总大小)。你能解释一下你的意思吗?
  • 另请注意,Parallel 仅对程序的 CPU 绑定部分有益。可能有一些 CPU 活动可以并行化,但我怀疑您的大部分时间都花在 I/O 上,这不会从并行化中受益。
  • @DStanley -- 和内存分配/页面交换。
  • @Tasha -- 你不需要void 进行递归。或者你可以,并且只使用你不断添加的输出参数。由你决定。

标签: c#


【解决方案1】:

并行访问同一物理驱动器不会加快工作速度。

您的主要问题是GetFiles 方法。它遍历收集所有文件名的所有子文件夹。然后你再次循环传递相同的文件。

请改用EnumerateFiles 方法。

试试这个代码。它会快得多。

public long GetDirectorySize(string path)
{
    var dirInfo = new DirectoryInfo(path);
    long totalSize = 0;

    foreach (var fileInfo in dirInfo.EnumerateFiles("*.*", SearchOption.AllDirectories))
    {
        totalSize += fileInfo.Length;
    }
    return totalSize;
}

MSDN:

EnumerateFiles 和 GetFiles 方法的区别如下: 使用 EnumerateFiles 时,可以在返回整个集合之前开始枚举名称集合;当您使用 GetFiles 时,您必须等待返回整个名称数组,然后才能访问该数组。因此,当您处理许多文件和目录时,EnumerateFiles 会更高效。

【讨论】:

  • 为什么枚举文件会更快?你能再解释一下吗?
  • 嗯,从 3 小时缩短到 2 小时左右,速度有点快。我们还有什么办法可以在 30 分钟左右摆脱吗?
【解决方案2】:

我不得不做类似的事情,但不是针对文件夹/文件大小。

我手头没有代码,但我使用以下代码作为入门。如果目录中有足够的文件,则并行执行

来源MSDN

以下示例按顺序迭代目录,但 并行处理文件。这可能是最好的方法 当您的文件与目录的比率很大时。也可以 并行化目录迭代,并访问每个文件 依次。并行化两个循环可能效率不高 除非您专门针对具有大量 处理器。但是,与所有情况一样,您应该测试您的应用程序 彻底确定最佳方法。

   static void Main()
   {            
      try 
      {
         TraverseTreeParallelForEach(@"C:\Program Files", (f) =>
         {
            // Exceptions are no-ops.
            try {
               // Do nothing with the data except read it.
               byte[] data = File.ReadAllBytes(f);
            }
            catch (FileNotFoundException) {}
            catch (IOException) {}
            catch (UnauthorizedAccessException) {}
            catch (SecurityException) {}
            // Display the filename.
            Console.WriteLine(f);
         });
      }
      catch (ArgumentException) {
         Console.WriteLine(@"The directory 'C:\Program Files' does not exist.");
      }   

      // Keep the console window open.
      Console.ReadKey();
   }

   public static void TraverseTreeParallelForEach(string root, Action<string> action)
   {
      //Count of files traversed and timer for diagnostic output
      int fileCount = 0;
      var sw = Stopwatch.StartNew();

      // Determine whether to parallelize file processing on each folder based on processor count.
      int procCount = System.Environment.ProcessorCount;

      // Data structure to hold names of subfolders to be examined for files.
      Stack<string> dirs = new Stack<string>();

      if (!Directory.Exists(root)) {
             throw new ArgumentException();
      }
      dirs.Push(root);

      while (dirs.Count > 0) {
         string currentDir = dirs.Pop();
         string[] subDirs = {};
         string[] files = {};

         try {
            subDirs = Directory.GetDirectories(currentDir);
         }
         // Thrown if we do not have discovery permission on the directory.
         catch (UnauthorizedAccessException e) {
            Console.WriteLine(e.Message);
            continue;
         }
         // Thrown if another process has deleted the directory after we retrieved its name.
         catch (DirectoryNotFoundException e) {
            Console.WriteLine(e.Message);
            continue;
         }

         try {
            files = Directory.GetFiles(currentDir);
         }
         catch (UnauthorizedAccessException e) {
            Console.WriteLine(e.Message);
            continue;
         }
         catch (DirectoryNotFoundException e) {
            Console.WriteLine(e.Message);
            continue;
         }
         catch (IOException e) {
            Console.WriteLine(e.Message);
            continue;
         }

         // Execute in parallel if there are enough files in the directory.
         // Otherwise, execute sequentially.Files are opened and processed
         // synchronously but this could be modified to perform async I/O.
         try {
            if (files.Length < procCount) {
               foreach (var file in files) {
                  action(file);
                  fileCount++;                            
               }
            }
            else {
               Parallel.ForEach(files, () => 0, (file, loopState, localCount) =>
                                            { action(file);
                                              return (int) ++localCount;
                                            },
                                (c) => {
                                          Interlocked.Add(ref fileCount, c);                          
                                });
            }
         }
         catch (AggregateException ae) {
            ae.Handle((ex) => {
                         if (ex is UnauthorizedAccessException) {
                            // Here we just output a message and go on.
                            Console.WriteLine(ex.Message);
                            return true;
                         }
                         // Handle other exceptions here if necessary...

                         return false;
            });
         }

         // Push the subdirectories onto the stack for traversal.
         // This could also be done before handing the files.
         foreach (string str in subDirs)
            dirs.Push(str);
      }

      // For diagnostic purposes.
      Console.WriteLine("Processed {0} files in {1} milleseconds", fileCount, sw.ElapsedMilliseconds);
   }

【讨论】:

    【解决方案3】:

    不幸的是,没有隐藏的托管 API 或 Win32 API 可以让您在不递归的情况下获取磁盘上文件夹的大小,否则 Windows 资源管理器肯定会利用它。

    这是一个示例方法,可以并行化您可以与标准非并行递归函数进行比较以实现相同的工作:

    private static long GetFolderSize(string sourceDir)
    {
        long size = 0;
        string[] fileEntries = Directory.GetFiles(sourceDir);
    
        foreach (string fileName in fileEntries)
        {
            Interlocked.Add(ref size, (new FileInfo(fileName)).Length);
        }
    
        var subFolders = Directory.EnumerateDirectories(sourceDir);
    
        var tasks = subFolders.Select(folder => Task.Factory.StartNew(() =>
        {
            if ((File.GetAttributes(folder) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint)
            {
                Interlocked.Add(ref size, (GetFolderSize(folder)));
                return size;
            }
            return 0;
        }));
    
        Task.WaitAll(tasks.ToArray());
    
        return size;
    }
    

    除非您在单个文件夹中有数百万个文件,否则此示例不会消耗大量内存。

    【讨论】:

    • 您使用什么库来获取 Task 作为关键字?
    【解决方案4】:

    使用Microsoft Scripting Runtime 似乎快了大约 90%:

    var fso = new Scripting.FileSystemObject();
    double size = fso.GetFolder(path).Size;
    

    参考:What is the fastest way to calculate a Windows folders size?

    【讨论】:

    • 图书馆叫什么我好像找不到
    • 它在 COM 选项卡中。路径类似于C:\Windows\SysWOW64\scrrun.dll
    • 是什么让它这么快?
    • 我猜它获取信息的系统调用要少得多。这更像是使用FileInfo 更慢,因为它获取更多信息并且可能需要为每个FileInfo.Length referencesource.microsoft.com/#mscorlib/system/io/… 单独的系统调用
    • 我也确信有更快的方法可以并行执行,因为例如 TreeSize Free 可以在不到一分钟的时间内获取我 C 驱动器上所有文件和文件夹的大小,但我没有不知道怎么做。
    猜你喜欢
    • 1970-01-01
    • 2012-06-16
    • 2012-01-28
    • 1970-01-01
    • 2010-09-11
    • 2022-07-06
    • 2013-02-17
    • 1970-01-01
    • 2016-03-08
    相关资源
    最近更新 更多