GHC không ghi nhớ chức năng.
Tuy nhiên, nó tính toán bất kỳ biểu thức đã cho nào trong mã một lần mỗi lần biểu thức lambda xung quanh của nó được nhập hoặc tối đa một lần nếu nó ở cấp cao nhất. Xác định nơi lambda-biểu thức là có thể là một chút khó khăn khi bạn sử dụng cú pháp đường như trong ví dụ của bạn, vì vậy hãy chuyển đổi các cú pháp khử đường tương đương:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Lưu ý: Báo cáo Haskell 98 thực sự mô tả một nhà khai thác trái phần như (a %)
như tương đương với \b -> (%) a b
, nhưng GHC desugars nó để (%) a
. đây là những kỹ thuật khác nhau vì chúng có thể được phân biệt bởi seq
. tôi nghĩ rằng tôi có thể đã đệ trình một vé GHC Trác về việc này.)
vì điều này, bạn có thể thấy rằng trong m1'
, biểu thức filter odd [1..]
không được chứa i n bất kỳ biểu thức lambda nào, do đó, nó sẽ chỉ được tính một lần cho mỗi lần chạy chương trình của bạn, trong khi ở m2'
, filter odd [1..]
sẽ được tính mỗi lần biểu thức lambda được nhập, tức là mỗi cuộc gọi của m2'
. Điều đó giải thích sự khác biệt về thời gian bạn đang thấy.
Thực tế, một số phiên bản của GHC, với các tùy chọn tối ưu nhất định, sẽ chia sẻ nhiều giá trị hơn mô tả ở trên cho biết. Điều này có thể có vấn đề trong một số trường hợp. Ví dụ, hãy xem xét các chức năng
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC thể nhận thấy rằng y
không phụ thuộc vào x
và viết lại các chức năng để
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
Trong trường hợp này, phiên bản mới là rất ít hiệu quả vì nó sẽ có để đọc khoảng 1 GB từ bộ nhớ trong đó y
được lưu trữ, trong khi phiên bản gốc sẽ chạy trong không gian cố định và phù hợp với bộ nhớ cache của bộ xử lý. Thực tế, theo GHC 6.12.1, hàm f
nhanh gấp hai lần khi biên dịch mà không cần tối ưu hóa so với được biên dịch bằng -O2
.
Chi phí để đánh giá (lọc lẻ [1 ..]) biểu thức gần bằng không anyway - đó là danh sách lười biếng sau khi tất cả, vì vậy chi phí thực tế là trong (x !! 10000000) ứng dụng khi danh sách thực sự được đánh giá. Bên cạnh đó, cả m1 và m2 dường như chỉ được đánh giá một lần với -O2 và -O1 (trên ghc của tôi 6.12.3) ít nhất là trong thử nghiệm sau đây: (test = m1 10000000 'seq' m1 10000000). Có một sự khác biệt mặc dù không có cờ tối ưu nào được chỉ định. Và cả hai biến thể của "f" của bạn đều có tối đa 5356 byte bất kể tối ưu hóa, bằng cách này (với tổng phân bổ ít hơn khi sử dụng -O2). –
@ Ed'ka: Hãy thử chương trình thử nghiệm này, với định nghĩa ở trên là 'f':' main = tương tác $ unlines. (hiển thị. bản đồ f. đọc). dòng'; biên dịch có hoặc không có '-O2'; sau đó 'echo 1 | ./main'. Nếu bạn viết một bài kiểm tra như 'main = print (f 5)', thì 'y' có thể là rác được thu thập khi nó được sử dụng và không có sự khác biệt giữa hai' f '. –
er, tất nhiên phải là 'map (show. F. Read)'. Và bây giờ tôi đã tải xuống GHC 6.12.3, tôi thấy kết quả tương tự như trong GHC 6.12.1. Và có, bạn là đúng về bản gốc 'm1' và' m2': phiên bản của GHC thực hiện loại nâng này với tối ưu hóa được kích hoạt sẽ biến 'm2' thành' m1'. –