GHC không ghi nhớ các chức năng.
Tuy nhiên, nó tính toán bất kỳ biểu thức nhất định nào trong mã mỗi lần mà biểu thức lambda xung quanh nó được nhập hoặc nhiều nhất một lần nếu nó ở cấp cao nhất. Việc xác định vị trí của các biểu thức lambda có thể hơi phức tạp khi bạn sử dụng cú pháp đường như trong ví dụ của mình, vì vậy hãy chuyển đổi chúng thành cú pháp gỡ bỏ 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ả phần toán tử bên trái (a %)
tương đương với \b -> (%) a b
, nhưng GHC gỡ bỏ nó thành (%) a
. Các phần này khác nhau về mặt kỹ thuật vì chúng có thể được phân biệt bởi seq
. Tôi nghĩ rằng tôi có thể đã gửi một phiếu GHC Trac về điều này.)
Với đ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 trong bất kỳ biểu thức lambda nào, vì vậy 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 ở trong m2'
, filter odd [1..]
sẽ được tính mỗi khi biểu thức lambda được nhập, tức là trên mỗi cuộc gọi của m2'
. Điều đó giải thích sự khác biệt về thời gian mà bạn đang thấy.
Trên thực tế, một số phiên bản của GHC, với các tùy chọn tối ưu hóa nhất định, sẽ chia sẻ nhiều giá trị hơn so với mô tả ở trên. Điều này có thể có vấn đề trong một số tình huống. Ví dụ, hãy xem xét chức năng
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC có thể nhận thấy điều y
đó không phụ thuộc vào x
và viết lại hàm thành
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
Trong trường hợp này, phiên bản mới kém hiệu quả hơn nhiều vì nó sẽ phải đọc khoảng 1 GB từ bộ nhớ y
được lưu trữ, trong khi phiên bản gốc sẽ chạy trong không gian không đổi và nằm gọn trong bộ nhớ đệm của bộ xử lý. Trên thực tế, theo GHC 6.12.1, hàm f
này nhanh hơn gần gấp đôi khi được biên dịch mà không cần tối ưu hóa so với khi được biên dịch cùng -O2
.
seq
m1 10000000). Có một sự khác biệt mặc dù khi không có cờ tối ưu hóa nào được chỉ định. Và cả hai biến thể của "f" của bạn đều có dung lượng tối đa là 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 -O2 được sử dụng).