Tôi có thể thêm một số chi tiết quan trọng về hệ thống tối ưu hóa của GHC.
Định nghĩa ngây thơ của foldr
chuyển xung quanh một hàm. Có một chi phí vốn có trong việc gọi một hàm - đặc biệt khi hàm không được biết tại thời gian biên dịch. Nó sẽ được thực sự tốt đẹp để có thể inline định nghĩa của hàm nếu nó được biết đến tại thời gian biên dịch.
Có các thủ thuật có sẵn để thực hiện nội tuyến đó trong GHC - và đây là một ví dụ về chúng. Đầu tiên, cần foldr
nội tuyến (tôi sẽ giải thích lý do sau). foldr
's thực hiện ngây thơ là đệ quy, vì vậy không thể được inlined. Vì vậy, một chuyển đổi công nhân/wrapper được áp dụng cho định nghĩa. Người lao động là đệ quy, nhưng wrapper thì không. Điều này cho phép foldr
được inlined, mặc dù đệ quy về cấu trúc của danh sách.
Khi số foldr
được gạch chân, nó cũng tạo bản sao của tất cả các liên kết cục bộ của nó. Đó là nhiều hơn hoặc ít hơn một văn bản trực tiếp nội tuyến (modulo một số đổi tên, và xảy ra sau khi vượt qua desugaring). Đây là nơi mà mọi thứ trở nên thú vị. go
là một ràng buộc địa phương, và trình tối ưu hóa được nhìn vào bên trong nó. Nó nhận thấy rằng nó gọi một hàm trong phạm vi cục bộ, có tên là k
. GHC thường sẽ xóa hoàn toàn biến số k
và chỉ thay thế bằng biểu thức k
. Và sau đó, nếu ứng dụng chức năng có khả năng thích hợp với nội tuyến, nó có thể được sắp xếp tại thời điểm này - loại bỏ toàn bộ phí gọi hàm đầu tiên hoàn toàn.
Hãy xem xét một ví dụ đơn giản, cụ thể. Chương trình này sẽ echo một dòng đầu vào với tất cả trailing 'x'
ký tự loại bỏ:
dropR :: Char -> String -> String
dropR x r = if x == 'x' && null r then "" else x : r
main :: IO()
main = do
s <- getLine
putStrLn $ foldr dropR "" s
Đầu tiên, tôi ưu hoa sẽ inline foldr
's định nghĩa và đơn giản hóa, kết quả là mã mà trông giống như sau:
main :: IO()
main = do
s <- getLine
-- I'm changing the where clause to a let expression for the sake of readability
putStrLn $ let { go [] = ""; go (x:xs) = dropR x (go xs) } in go s
Và đó là điều chuyển đổi công nhân-wrapper cho phép .. Tôi sẽ bỏ qua các bước còn lại, nhưng nó phải được rõ ràng rằng GHC bây giờ có thể inline định nghĩa của dropR
, loại bỏ các chức năng gọi trên không. Đây là nơi chiến thắng hiệu suất lớn đến từ.
Một chi tiết chưa được đề cập: ghc chỉ inline một chức năng khi nó được áp dụng đầy đủ, * cú pháp *, ở phía bên tay trái của nó. Điều này là khá kỳ lạ và xấu xí nếu bạn đang sử dụng để suy nghĩ về currying và tạo mã điểm-Việt-phong cách tốt đẹp. Đó là lý do tại sao đôi khi bạn thấy lambdas ngớ ngẩn ở bên phải của '=' trong mã được tối ưu hóa. – jberryman