2015-05-18 19 views
8

Tôi quan tâm đến việc khi nào và khi giá trị lớp "toàn cầu" đa hình "" được chia sẻ/ghi nhớ, đặc biệt là trên các ranh giới mô-đun. Tôi đã đọc thisthis, nhưng chúng dường như không phản ánh hoàn cảnh của tôi và tôi thấy một số hành vi khác với những gì người ta có thể mong đợi từ câu trả lời.sử dụng lại/ghi nhớ các giá trị đa hình toàn cầu (lớp) trong Haskell

Hãy xem xét một lớp học đó cho thấy nhiều giá trị mà có thể tốn kém để tính toán:

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-} 

module A 

import Debug.Trace 

class Costly a where 
    costly :: a 

instance Num i => Costly i where 
    -- an expensive (but non-recursive) computation 
    costly = trace "costly!" $ (repeat 1) !! 10000000 

foo :: Int 
foo = costly + 1 

costlyInt :: Int 
costlyInt = costly 

Và một module riêng biệt:

module B 
import A 

bar :: Int 
bar = costly + 2 

main = do 
    print foo 
    print bar 
    print costlyInt 
    print costlyInt 

Chạy main mang hai đánh giá riêng của costly (như được chỉ ra bởi các dấu vết): một cho số foo và một cho số bar. Tôi biết rằng costlyInt chỉ trả lại (được đánh giá) costly từ foo, bởi vì nếu tôi xóa print foo từ main thì costlyInt đầu tiên trở nên tốn kém. (. Tôi cũng có thể gây ra costlyInt để thực hiện một đánh giá riêng biệt không có vấn đề gì, bằng cách khái quát các loại foo-Num a => a)

Tôi nghĩ rằng tôi biết lý do tại sao hành vi này xảy ra: trường hợp của Costly là một cách hiệu quả là một chức năng mà phải mất một Num từ điển và tạo một từ điển Costly. Vì vậy, khi biên dịch bar và giải quyết tham chiếu đến costly, ghc sẽ tạo một từ điển Costly mới, có một phần lớn trong đó. Câu hỏi 1: Tôi có đúng về điều này không?

Có một vài cách để gây ra chỉ là một đánh giá costly, bao gồm:

  • Đặt tất cả mọi thứ trong một module.
  • Xóa ràng buộc đối tượng Num i và chỉ cần xác định trường hợp Costly Int.

Thật không may, các tương tự của các giải pháp này không khả thi trong chương trình của tôi - tôi có một số mô-đun sử dụng giá trị lớp trong dạng đa hình của nó và chỉ trong tệp nguồn cấp cao nhất.

Ngoài ra còn có sự thay đổi khi không giảm số lượng đánh giá, chẳng hạn như:

  • Sử dụng INLINE, INLINABLE, hoặc NOINLINE trên costly định nghĩa trong ví dụ. (Tôi đã không mong đợi điều này để làm việc, nhưng hey, giá trị một shot.)
  • Sử dụng một SPECIALIZE instance Costly Int pragma trong định nghĩa cá thể.

Điều sau thật đáng ngạc nhiên đối với tôi - tôi cho rằng về cơ bản nó tương đương với mục thứ hai phía trên rằng đã hoạt động. Tức là, tôi nghĩ rằng nó sẽ tạo ra một từ điển đặc biệt Costly Int, tất cả là foo, barcostlyInt sẽ chia sẻ. Câu hỏi của tôi 2: Tôi đang thiếu gì ở đây?

Câu hỏi cuối cùng của tôi: có cách nào tương đối đơn giản và dễ hiểu để nhận những gì tôi muốn, tức là tất cả các tham chiếu đến costly của loại cụ thể được chia sẻ trên các mô-đun? Từ những gì tôi đã nhìn thấy cho đến nay, tôi nghi ngờ câu trả lời là không, nhưng tôi vẫn giữ hy vọng.

+0

Câu hỏi này chính xác như thế nào so với [câu trước của tôi] (http://stackoverflow.com/questions/25057803/is-there-an-automatic-way-to-memoise-global-polymorphic-values-in- haskell)? – leftaroundabout

+0

Từ những gì tôi có thể nói, chi phí trong câu hỏi của bạn xuất phát từ đệ quy kết hợp với việc chuyển từ điển, điều này gây ra sự đánh giá lại theo cấp số nhân. Một khi sau được cố định, tính toán chính nó là nhanh chóng. Nhưng nếu hai mô-đun được gọi là fibsImplicitDict, chúng sẽ nhận được các khối khác nhau sẽ được đánh giá riêng biệt. Tôi không thể đủ khả năng đó, bởi vì tính toán của tôi vốn dĩ đắt tiền (không phải do đệ quy). –

+0

PS - thực tế là có những trường hợp lớp với các ràng buộc trên chúng cũng đóng một vai trò ở đây, có vẻ như. –

Trả lời

6

Kiểm soát chia sẻ rất khó khăn trong GHC. Có nhiều tối ưu hóa mà GHC thực hiện có thể ảnh hưởng đến việc chia sẻ (chẳng hạn như nội tuyến, làm nổi bật, vv).

Trong trường hợp này, để trả lời câu hỏi tại sao pragma chuyên không đạt được hiệu quả mong muốn, chúng ta hãy nhìn vào cốt lõi của mô-đun B, đặc biệt là trong những bar chức năng:

Rec { 
bar_xs 
bar_xs = : x1_r3lO bar_xs 
end Rec } 

bar1 = $w!! bar_xs 10000000 
--  ^^^ this repeats the computation. bar_xs is just repeat 1 

bar = 
    case trace $fCostlyi2 bar1 of _ { I# x_aDm -> I# (+# x_aDm 2) } 
    --   ^^^ this is just the "costly!" string 

Đó didn' t làm việc như chúng tôi muốn. Thay vì sử dụng lại costly, GHC đã quyết định chỉ nội dòng chức năng costly.

Vì vậy, chúng tôi phải ngăn GHC từ nội tuyến tốn kém hoặc tính toán sẽ bị trùng lặp. làm sao chúng ta làm việc đó bây giờ? Bạn có thể nghĩ thêm một pragma {-# NOINLINE costly #-} sẽ là đủ, nhưng tiếc là chuyên môn mà không cần nội tuyến dường như không làm việc cùng nhau tốt:

A.hs:13:3: Warning: 
    Ignoring useless SPECIALISE pragma for NOINLINE function: ‘$ccostly’ 

Nhưng có một thủ thuật để thuyết phục GHC để làm những gì chúng ta muốn: chúng ta có thể viết costly theo cách sau:

instance Num i => Costly i where 
    -- an expensive (but non-recursive) computation 
    costly = memo where 
    memo :: i 
    memo = trace "costly!" $ (repeat 1) !! 10000000 
    {-# NOINLINE memo #-} 
    {-# SPECIALIZE instance Costly Int #-} 
-- (this might require -XScopedTypeVariables) 

Điều này cho phép chúng tôi chuyên costly, sẽ đồng thời tránh nội tuyến tính toán của chúng tôi.

+0

Xuất sắc! Tôi đã thử các thủ thuật ghi nhớ, và NOINLINE với SPECIALIZE (chỉ để có được cùng một lỗi), nhưng đã không nghĩ rằng để kết hợp chúng theo cách này. Thủ thuật này sẽ làm việc cho một số giá trị toàn cầu của tôi, nhưng đối với những người khác tôi không thể CHUYÊN MÔN vì tôi chỉ biết các loại cụ thể ở mô-đun ứng dụng trên cùng. Có hy vọng nào cho trường hợp đó không? –

+0

@ChrisPeikert Tôi chưa kiểm tra, nhưng có lẽ nội tuyến 'tốn kém' có thể đạt được hiệu quả tương tự như chuyên môn trong trường hợp này? – bennofs

+0

Ah không, nội tuyến không hoạt động: | – bennofs

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