2017-04-16 74 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

謝謝斯蒂芬:) –