2012-03-14 22 views
8

Nếu tôi di chuyển một hàm từ nơi nó được sử dụng thành một mô-đun riêng biệt, tôi nhận thấy hiệu suất của chương trình giảm đáng kể.Hiệu suất thay đổi đáng kể nếu một hàm được di chuyển giữa các mô-đun

calc = sum . nub . map third . filter isProd . concat . map parts . permutations 
    where third (_,_,b)   = fromDigits b 
      isProd (a,b,p)   = fromDigits a * fromDigits b == fromDigits p 
      -- All possibilities have digits: A x AAAA or AA x AAA 
      parts (a:b:c:d:e:rest) = [([a], [b,c,d,e], rest) 
            ,([a,b], [c,d,e], rest)] 

trong mô-đun khác:

fromDigits :: Integral a => [a] -> a         
fromDigits = foldl1' (\a b -> 10 * a + b) 

này chạy trong 0,1 giây khi fromDigits là trong module tương tự, nhưng 0,4 giây khi tôi di chuyển nó đến mô-đun khác. Tôi cho rằng điều này là do GHC không thể thực hiện chức năng nếu nó ở trong mô-đun khác nhau, nhưng tôi cảm thấy nó là nên có thể, vì chúng nằm trong cùng một gói.

Tôi không chắc chắn cài đặt trình biên dịch là gì, nhưng nó được xây dựng với giá trị mặc định của Leksah/cabal. Tôi khá chắc chắn đó là với -O2 là tối thiểu.

+1

GHC có thể nội dòng trên các mô-đun. Hãy xem điều này để biết một số mẹo: http://www.haskell.org/haskellwiki/Performance/GHC#Modules_and_separate_compilation – porges

+1

Bạn có thể thử thêm '{- # INLINE fromDigits # -}' -kết quả gần định nghĩa 'fromDigits' ... – hvr

+0

Cảm ơn, pragma INLINE hoạt động. Nhưng .. nó cảm thấy một chút lạ để không thể xác định rằng tại trang web cuộc gọi. –

Trả lời

8

Đối với các loại lớp đa hình fromDigits, bạn sẽ có được một hàm có nghĩa là, do sự tra cứu từ điển cho (+), (*)fromInteger, quá lớn để có nó mở ra tự động tiếp xúc. Điều đó có nghĩa là nó không thể được chuyên biệt tại các trang web cuộc gọi và tra cứu từ điển không thể được loại bỏ để có thể bổ sung nội tuyến và nhân (có thể cho phép tối ưu hóa hơn nữa).

Khi được xác định trong cùng một mô-đun khi được sử dụng, với việc tối ưu hóa, GHC tạo phiên bản dành riêng cho loại được sử dụng tại, nếu điều đó được biết. Sau đó, tra cứu từ điển có thể được loại bỏ và các hoạt động (+)(*) có thể được gạch chân (nếu loại chúng được sử dụng tại đó có các thao tác phù hợp với nội tuyến).

Nhưng điều đó phụ thuộc vào loại được biết. Vì vậy, nếu bạn có đa hình calcfromDigits trong một mô-đun, nhưng chỉ sử dụng trong một mô-đun khác, bạn lại ở vị trí chỉ có phiên bản chung, nhưng do phiên bản mở ra không được phơi ra, nó không thể chuyên biệt hoặc được tối ưu hóa tại trang web cuộc gọi.

Một giải pháp là làm cho việc mở ra hàm được hiển thị trong tệp giao diện, do đó có thể được tối ưu hóa đúng nơi nó được sử dụng, khi dữ liệu cần thiết (đặc biệt là loại) khả dụng. Bạn có thể phơi bày hàm đang mở ra trong tệp giao diện bằng cách thêm {-# INLINE #-} hoặc, như của GHC 7, một pragma {-# INLINABLE #-} vào hàm. Điều đó làm cho mã nguồn gần như không thay đổi có sẵn khi biên dịch mã gọi, vì vậy chức năng có thể được tối ưu hóa đúng cách với nhiều thông tin hơn. Một trong những điều ngược lại là code-bloat, bạn sẽ nhận được một bản sao của mã được tối ưu hóa ở mọi địa điểm cuộc gọi (đối với INLINABLE nó không quá khắc nghiệt, bạn có ít nhất một bản sao cho mỗi mô-đun gọi, thường không quá tệ).

Một giải pháp thay thế là để tạo ra các phiên bản đặc biệt trong module xác định bằng cách thêm {-# SPECIALISE #-} pragmas (đánh vần Mỹ cũng được chấp nhận) để cho GHC tạo các phiên bản được tối ưu hóa cho các loại quan trọng (Int, Integer, Word,?). Điều đó cũng tạo ra các quy tắc viết lại, do đó sử dụng ở các kiểu chuyên dùng cho được viết lại để sử dụng phiên bản chuyên biệt (khi biên dịch với các tối ưu hóa).

Nhược điểm của việc này là một số tối ưu hóa có thể xảy ra khi mã được gạch chân không.

+2

Sự hiểu biết của tôi là với '{- # INLINABLE # -}', bạn có thể chia sẻ các chuyên môn, vì vậy nó không quá tệ như một bản sao ở mọi trang cuộc gọi. – Anthony

+0

Đúng. Thêm vào đó như là một suy nghĩ, quên cập nhật nhận xét code-bloat. Cảm ơn vì đã phát hiện ra điều đó. –

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