Tôi vừa đọc xong "Lập trình đồng thời trên JVM" của Venkat Subramaniam và trong cuốn sách đó, tác giả sử dụng như một ví dụ của mình, đếm kích thước tệp trong cây thư mục. Ông cho thấy việc triển khai không sử dụng đồng thời, sử dụng hàng đợi, sử dụng chốt và sử dụng diễn viên scala. Trên hệ thống của tôi, tất cả các triển khai đồng thời (hàng đợi, chốt và diễn viên scala) đều có thể chạy dưới 9 giây khi lặp qua thư mục/usr của tôi (OSX 10.6.8, Core Duo 2 Ghz, Intel G1 SSD 160GB).Thuật toán tập tin hệ thống đệ quy có nên được xử lý theo kiểu bắt buộc không?
Tôi đang học Clojure và quyết định chuyển phiên bản diễn viên Scala tới Clojure bằng tác nhân. Thật không may, tôi đã trung bình 11-12 giây chậm hơn đáng kể so với những người khác. Sau khi chi tiêu NGÀY kéo tóc của tôi ra, tôi phát hiện ra rằng các bit mã sau đây là thủ phạm (processFile là một chức năng mà tôi gửi cho các đại lý tập tin xử lý (s):
(defn processFile
[fileProcessor collectorAgent ^String fileName]
(let [^File file-obj (File. ^String fileName)
fileTotals (transient {:files 0, :bytes 0})]
(cond
(.isDirectory file-obj)
(do
(doseq [^File dir (.listFiles file-obj) :when (.isDirectory dir)]
(send collectorAgent addFileToProcess (.getPath dir)))
(send collectorAgent tallyResult *agent*)
(reduce (fn [currentTotal newItem] (assoc! currentTotal :files (inc (:files currentTotal))
:bytes (+ (:bytes currentTotal) newItem)))
fileTotals
(map #(.length ^File %) (filter #(.isFile ^File %) (.listFiles file-obj))))
(persistent! fileTotals))
(.isFile file-obj) (do (send collectorAgent tallyResult *agent*) {:files 1, :bytes (.length file-obj)}))))
Bạn sẽ nhận thấy tôi đã cố gắng sử dụng . kiểu gợi ý và một thoáng để cải thiện hiệu suất, tất cả không có kết quả tôi đã thay thế đoạn mã trên như sau:
(defn processChildren
[children]
(loop [entries children files 0 bytes 0 dirs '()]
(let [^File child (first entries)]
(cond
(not (seq entries)) {:files files, :bytes bytes, :dirs dirs}
(.isFile child) (recur (rest entries) (inc files) (+ bytes (.length child)) dirs)
(.isDirectory child) (recur (rest entries) files bytes (conj dirs child))
:else (recur (rest entries) files bytes dirs)))))
(defn processFile
[fileProcessor collectorAgent ^String fileName]
(let [{files :files, bytes :bytes, dirs :dirs} (processChildren (.listFiles (File. fileName)))]
(doseq [^File dir dirs]
(send collectorAgent addFileToProcess (.getPath dir)))
(send collectorAgent tallyResult *agent*)
{:files files, :bytes bytes}))
phiên bản này được thực hiện trên mệnh nếu không nhanh hơn so với phiên bản Scala và gần như đồng nhất với thuật toán được sử dụng trong phiên bản Scala. Tôi chỉ đơn giản giả định rằng cách tiếp cận chức năng cho thuật toán cũng sẽ hoạt động tốt.
Vì vậy, ... câu hỏi dài này cuộn xuống những điều sau đây: Tại sao phiên bản thứ hai nhanh hơn?
Giả thuyết của tôi là mặc dù phiên bản đầu tiên sử dụng bản đồ/bộ lọc/giảm trên nội dung của thư mục có nhiều chức năng hơn quá trình xử lý thư mục thứ hai, nó kém hiệu quả hơn nhiều vì nội dung của thư mục đang được lặp lại qua nhiều lần. Kể từ khi hệ thống tập tin I/O là chậm, toàn bộ chương trình bị.Giả sử tôi đúng, thì không an toàn khi nói rằng bất kỳ thuật toán hệ thống tệp đệ quy nào cũng sẽ thích cách tiếp cận bắt buộc để thực hiện?
Tôi là người mới bắt đầu ở Clojure vì vậy, tôi có thể tự do trích xuất mã của mình thành từng mảnh nếu tôi làm điều gì đó ngu ngốc hoặc không thành ngữ.