2017-04-16 72 views
3

阅读ForkJoinPool后,我尝试了一个实验来测试速度有多快实际上ForkJoinPool是,相比于普通的递归时。ForkJoinPool VS平原递归

我计算出的文件数的文件夹中递归,和我surprize,普通递归执行的方式优于ForkJoinPool

这里是我的代码。

递归任务

class DirectoryTask extends RecursiveTask<Long> { 

    private Directory directory; 

    @Override 
    protected Long compute() { 
     List<RecursiveTask<Long>> forks = new ArrayList<>(); 
     List<Directory> directories = directory.getDirectories(); 
     for (Directory directory : directories) { 
      DirectoryTask directoryTask = new DirectoryTask(directory); 
      forks.add(directoryTask); 
      directoryTask.fork(); 
     } 
     Long count = directory.getDoumentCount(); 
     for (RecursiveTask<Long> task : forks) { 
      count += task.join(); 
     } 
     return count; 
    } 
} 

平原递归

private static Long getFileCount(Directory directory) { 
     Long recursiveCount = 0L; 
     List<Directory> directories = directory.getDirectories(); 
     if (null != directories) { 
      for (Directory d : directories) { 
       recursiveCount += getFileCount(d); 
      } 
     } 
     return recursiveCount + directory.getDoumentCount(); 
    } 

Directory对象

class Directory { 

    private List<Directory> directories; 
    private Long doumentCount = 0L; 

    static Directory fromFolder(File file) { 
     List<Directory> children = new ArrayList<>(); 
     Long documentCount = 0L; 
     if (!file.isDirectory()) { 
      throw new IllegalArgumentException("Only directories are allowed"); 
     } 
     String[] files = file.list(); 
     if (null != files) { 
      for (String path : files) { 
       File f = new File(file.getPath() + File.separator + path); 
       if (f.isHidden()) { 
        continue; 
       } 
       if (f.isDirectory()) { 
        children.add(Directory.fromFolder(f)); 
       } else { 
        documentCount++; 
       } 
      } 
     } 
     return new Directory(children, documentCount); 
    } 
} 

个结果

  • 平原递归:3ms的
  • ForkJoinPool:25ms的

在哪里,这里的错误?

我只是想了解是否有特定的阈值,低于该普通递归比ForkJoinPool更快。

回答

4

生活中没有任何东西是免费的。如果你必须将一辆啤酒箱从你的车上搬到你的公寓 - 那么更快:手动将它搬到那里,或者先到棚屋去让独轮车使用它来移动那个箱子?

创建线程对象是一个“原生”的操作,下降到底层操作系统到那里获取资源。这可能是一个相当昂贵的操作。

含义:就在一个问题扔“更多的线程”没有自动加快速度。与此相反的。当你的任务主要是CPU密集型时,并行处理可能会带来小的收益。当你做很多的IO时,然后有多个线程可以让你做更少的整体等待;从而提高您的吞吐量。

换句话说:叉/加入需要大量的活动它的真正的工作之前。将它用于只需要几毫秒的计算简直就是矫枉过正。因此:你会期待在更大的数据集上工作的“fork/join”操作。

对于进一步的阅读,你可能看parallel streams。那些使用封面下的fork/join框架;令人惊讶的是,期望任意parallelStream比普通流“更快”也是一种误解。

+0

非常感谢您的澄清。所以,我从这里的答案(Stephen C的一个), 当正在执行的任务实际需要一段时间才能完成时,ForkJoinPool是人为的,以补偿线程创建的开销。 –

+0

正确。除此之外:你还必须小心如何/你的基准。如果您多次重复实验时执行时间发生变化,这并不会让我感到惊讶。 – GhostCat

5

有多个方面,以这样的:

  1. 是否有串行(例如纯递归)之间并平行(例如forkjoin)解决方案,以同样的问题的差?

  2. 并行化文件系统访问的范围是什么?

  3. 什么是测量性能的陷阱

回答#1。是,有一点不同。并行性对于太小的问题不好。随着并行解决方案,你需要考虑的开销:

  • 创建和管理线程
  • 从父线程孩子传递信息线程
  • 从子线程父线程返回结果
  • 对共享数据结构的同步访问,
  • 等待最慢/最后完成子线程完成。

如何将这些在实践中发挥出依赖于各种各样的事情......包括问题的大小和并行性的机会。

#2的答案(可能)不像您想象的那么多。典型的文件系统存储在具有物理特性(例如磁盘旋转和磁盘头查找)的磁盘驱动器中。这些通常会成为瓶颈,而且您拥有高端存储系统的机会越来越少,并行性就没有太大的空间。

#3的答案是有很多陷阱。而且这些陷阱可能会导致非常误导(即无效)的性能结果......如果您不允许的话。最大的陷阱之一是JVM需要时间来“热身”;即加载类,执行JIT编译,调整堆大小等等。

另一个适用于执行文件系统I/O的基准测试的陷阱是典型的操作系统将执行缓存磁盘块和文件/目录元数据等操作。因此,第二次访问文件或目录时,它可能比第一次更快。


话虽如此,如果你有一个精心设计的,高性能文件系统(保持在SSD上如索引节点)和一个精心设计的应用程序,以及足够的核心,可以实现文件系统的非凡速度通过并行扫描。 (例如,在12小时内检查5亿个文件的修改时间戳....)

+0

谢谢斯蒂芬:) –