2011-07-07 25 views
6

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ữ.

Trả lời

4

Tôi đã chỉnh sửa phiên bản đầu tiên để làm cho phiên bản dễ đọc hơn. Tôi có một vài ý kiến, nhưng tuyên bố không có cách thuyết phục hữu ích:

  1. Bạn thêm transients và typehints không có bằng chứng thực như những gì đã được làm chậm điều xuống. Nó hoàn toàn có thể làm chậm mọi thứ xuống một cách đáng kể với ứng dụng bất cẩn của các hoạt động này, vì vậy bạn nên xem hồ sơ để tìm hiểu những gì thực sự làm chậm công cụ. Lựa chọn của bạn có vẻ hợp lý, nhưng tôi đã loại bỏ các loại mà rõ ràng là không có hiệu lực (ví dụ, trình biên dịch không cần gợi ý để biết rằng (File. ...) mang lại một đối tượng File).

  2. Clojure (thực sự, bất kỳ lisp) mạnh mẽ thích some-agent đến someAgent. Cú pháp tiền tố có nghĩa là không có lo lắng rằng - có thể được phân tích cú pháp dưới dạng phép trừ bởi trình biên dịch không cần thiết, vì vậy chúng ta có thể đủ khả năng cho các tên có khoảng cách rộng hơn.

  3. Bạn bao gồm các cuộc gọi tới một loạt các hàm mà bạn không xác định ở đây, như tallyResult và addFileToProcess.Có lẽ họ thực hiện tốt kể từ khi bạn đang sử dụng chúng trong phiên bản performant, nhưng bằng cách không bao gồm chúng bạn đã làm cho nó khó khăn cho bất cứ ai khác để poke xung quanh nó và xem những gì tốc độ lên.

  4. Cân nhắc việc gửi-off thay vì gửi cho các hoạt động I/O-bound: gửi sử dụng một luồng mạng bị chặn để tránh làm xáo trộn bộ xử lý của bạn. Ở đây điều này có lẽ không quan trọng vì bạn chỉ sử dụng một tác nhân và nó tuần tự hóa, nhưng trong tương lai bạn sẽ gặp phải những trường hợp quan trọng.

Dù sao, như đã hứa, một viết lại hơn-dễ đọc của phiên bản đầu tiên của bạn:

(defn process-file 
    [_ collector-agent ^String file-name] 
    (let [file-obj (File. file-name) 
     file-totals (transient {:files 0, :bytes 0})] 
    (cond (.isDirectory file-obj) 
      (do 
      (doseq [^File dir (.listFiles file-obj) 
        :when (.isDirectory dir)] 
       (send collector-agent addFileToProcess 
        (.getPath dir))) 
      (send collector-agent tallyResult *agent*) 
      (reduce (fn [current-total new-item] 
         (assoc! current-total 
           :files (inc (:files current-total)) 
           :bytes (+ (:bytes current-total) new-item))) 
        file-totals 
        (map #(.length ^File %) 
         (filter #(.isFile ^File %) 
           (.listFiles file-obj)))) - 
      (persistent! file-totals)) 

      (.isFile file-obj) 
      (do (send collector-agent tallyResult *agent*) 
       {:files 1, :bytes (.length file-obj)})))) 

Chỉnh sửa: Bạn đang sử dụng quá độ một cách chính xác, bằng cách ném đi những kết quả của giảm của bạn. (assoc! m k v)được phép để sửa đổi và trả lại đối tượng m nhưng có thể trả về một đối tượng khác nếu điều đó thuận tiện hơn hoặc hiệu quả hơn. Vì vậy, bạn cần một cái gì đó giống như (persistent! (reduce ...))

Các vấn đề liên quan