2016-12-21 16 views
7

GHC có bao giờ giải nén các loại tiền khi chuyển chúng vào các hàm không? Ví dụ, giả sử rằng chúng ta có các loại sau đây:Công ước gọi GHC đối với các đối số hàm kiểu số

data Foo 
    = Foo1 {-# UNPACK #-} !Int {-# UNPACK #-} !Word 
    | Foo2 {-# UNPACK #-} !Int 
    | Foo3 {-# UNPACK #-} !Word 

Sau đó, tôi xác định một chức năng đó là nghiêm ngặt trong Foo đối số của nó:

consumeFoo :: Foo -> Int 
consumeFoo x = case x of ... 

Khi chạy, khi tôi gọi consumeFoo, những gì tôi có thể mong đợi xảy ra? Các GHC calling convention là để vượt qua đối số trong sổ đăng ký (hoặc trên ngăn xếp khi có quá nhiều). Tôi có thể thấy hai cách mà đối số đi qua có thể đi:

  1. Một con trỏ đến một số Foo trên heap được chuyển thành một đối số.
  2. Một đại diện ba đối số của Foo được sử dụng, một đối số đại diện cho các nhà xây dựng dữ liệu đã được sử dụng và hai người kia đại diện cho thể IntWord giá trị trong các nhà xây dựng dữ liệu.

Tôi muốn đại diện thứ hai, nhưng tôi không biết nếu nó thực sự là những gì sẽ xảy ra. Tôi biết về việc tải xuống UnpackedSumTypes trong GHC 8.2, nhưng không rõ liệu nó có làm được những gì tôi muốn hay không. Thay vào đó, nếu tôi đã viết hàm như sau:

consumeFooAlt :: (# (# Int#, Word# #) | Int# | Word# #) -> Int 

Sau đó, tôi mong rằng đánh giá (2) sẽ là điều xảy ra. Và Unpacking section của trang khoản tiền giải nén chỉ ra rằng tôi có thể làm được điều này cũng như:

data Wrap = Wrap {-# UNPACK #-} !Foo 
consumeFooAlt2 :: Wrap -> Int 

Và đó cũng nên có các đại diện tôi muốn, tôi nghĩ. Vì vậy, câu hỏi của tôi là, không sử dụng loại trình bao bọc hoặc tổng số chưa được giải nén, làm thế nào tôi có thể đảm bảo rằng một khoản được giải nén vào sổ đăng ký (hoặc lên ngăn xếp) khi tôi chuyển nó làm đối số cho hàm? Nếu nó là có thể, nó là cái gì mà GHC 8.0 có thể đã làm, hoặc là nó một cái gì đó mà sẽ chỉ có sẵn trong GHC 8,2?

Trả lời

7

Đầu tiên: Tối ưu hóa được đảm bảo và GHC không kết hợp tốt. Do mức độ cao, rất khó để dự đoán mã mà GHC sẽ tạo ra trong mọi trường hợp. Cách duy nhất để chắc chắn là nhìn vào Core. Nếu bạn đang phát triển một ứng dụng phụ thuộc rất hiệu quả với GHC, thì bạn cần phải trở nên quen thuộc với Core I.

Tôi không biết về bất kỳ tối ưu hóa nào trong GHC thực hiện chính xác những gì bạn mô tả. Dưới đây là một chương trình ví dụ: cái nhìn đầu tiên

module Test where 

data Sum = A {-# UNPACK #-} !Int | B {-# UNPACK #-} !Int 

consumeSum :: Sum -> Int 
consumeSum x = case x of 
    A y -> y + 1 
    B y -> y + 2 

{-# NOINLINE consumeSumNoinline #-} 
consumeSumNoinline = consumeSum 

{-# INLINE produceSumInline #-} 
produceSumInline :: Int -> Sum 
produceSumInline x = if x == 0 then A x else B x 

{-# NOINLINE produceSumNoinline #-} 
produceSumNoinline :: Int -> Sum 
produceSumNoinline x = if x == 0 then A x else B x 

test :: Int -> Int 
--test x = consumeSum (produceSumInline x) 
test x = consumeSumNoinline (produceSumNoinline x) 

Hãy vào những gì sẽ xảy ra nếu chúng ta không inline consumeSum cũng không produceSum. Dưới đây là cốt lõi:

test :: Int -> Int 
test = \ (x :: Int) -> consumeSumNoinline (produceSumNoinline x) 

(sản xuất với ghc-core test.hs -- -dsuppress-unfoldings -dsuppress-idinfo -dsuppress-module-prefixes -dsuppress-uniques)

Ở đây, chúng ta có thể thấy rằng GHC (8.0 trong trường hợp này) không Unbox loại tiền thông qua như là một đối số chức năng. Không có gì thay đổi nếu chúng tôi nội tuyến consumeSum hoặc produceSum.

Nếu chúng ta inline cả tuy nhiên, sau đó các mã sau đây được tạo:

test :: Int -> Int 
test = 
    \ (x :: Int) -> 
    case x of _ { I# x1 -> 
    case x1 of wild1 { 
     __DEFAULT -> I# (+# wild1 2#); 
     0# -> lvl1 
    } 
    } 

gì đã xảy ra ở đây là thông qua nội tuyến, GHC kết thúc với:

\x -> case (if x == 0 then A x else B x) of 
    A y -> y + 1 
    B y -> y + 2 

nào thông qua các trường của -case (if chỉ là một đặc biệt case) được chuyển thành:

\x -> if x == 0 then case (A x) of ... else case (B x) of ... 

Bây giờ đó là một trường hợp với một nhà xây dựng nổi tiếng, vì vậy GHC có thể làm giảm trường hợp tại thời gian biên dịch kết thúc với:

\x -> if x == 0 then x + 1 else x + 2 

Vì vậy, nó loại bỏ hoàn toàn các nhà xây dựng.


Tóm lại, tôi tin rằng GHC không có khái niệm về loại "không được tổng hợp" trước phiên bản 8.2, cũng áp dụng cho đối số chức năng. Cách duy nhất để có được "unboxed" khoản tiền là để có được các nhà xây dựng loại bỏ hoàn toàn thông qua nội tuyến.

2

Nếu bạn cần tối ưu hóa như vậy, giải pháp đơn giản nhất của bạn là tự làm điều đó. Tôi nghĩ có thực sự nhiều cách để đạt được điều này, nhưng người ta là:

data Which = Left | Right | Both 
data Foo = Foo Which Int Word 

Việc mở kiểm tra bất kỳ lĩnh vực thuộc loại này là hoàn toàn không liên quan đến câu hỏi của 'hình dạng của các đại diện', đó là những gì bạn đang thực sự hỏi về. Các liệt kê đã được tối ưu hóa cao - chỉ có một giá trị cho mỗi hàm tạo được tạo ra - do đó việc bổ sung trường này không ảnh hưởng đến hiệu năng. Các đại diện giải nén của loại này là chính xác những gì bạn muốn - một từ cho Which constructor và một cho mỗi lĩnh vực.

Nếu bạn viết các chức năng của bạn theo cách phù hợp, bạn sẽ có được mã đúng:

data Which = Lft | Rgt | Both 
data Foo = Foo Which {-# UNPACK #-} !Int {-# UNPACK #-} !Word 

consumeFoo :: Foo -> Int 
consumeFoo (Foo w l r) = 
    case w of 
    Lft -> l 
    Rgt -> fromIntegral r 
    Both -> l + fromIntegral r 

Cốt lõi tạo ra là khá rõ ràng:

consumeFoo :: Foo -> Int 
consumeFoo = 
    \ (ds :: Foo) -> 
    case ds of _ { Foo w dt dt1 -> 
    case w of _ { 
     Lft -> I# dt; 
     Rgt -> I# (word2Int# dt1); 
     Both -> I# (+# dt (word2Int# dt1)) 
    } 
    } 

Tuy nhiên, đối với các chương trình đơn giản như:

consumeFoos = foldl' (+) 0 . map consumeFoo 

Tối ưu hóa này không có sự khác biệt. Như đã được nêu trong câu trả lời khác, chức năng nội consumeFoo chỉ là inlined:

Rec { 
$wgo :: [Foo] -> Int# -> Int# 
$wgo = 
    \ (w :: [Foo]) (ww :: Int#) -> 
    case w of _ { 
     [] -> ww; 
     : y ys -> 
     case y of _ { 
      Lft dt -> $wgo ys (+# ww dt); 
      Rgt dt -> $wgo ys (+# ww (word2Int# dt)); 
      Both dt dt1 -> $wgo ys (+# ww (+# dt (word2Int# dt1))) 
     } 
    } 
end Rec } 

vs

Rec { 
$wgo :: [Foo] -> Int# -> Int# 
$wgo = 
    \ (w :: [Foo]) (ww :: Int#) -> 
    case w of _ { 
     [] -> ww; 
     : y ys -> 
     case y of _ { Foo w1 dt dt1 -> 
     case w1 of _ { 
      Lft -> $wgo ys (+# ww dt); 
      Rgt -> $wgo ys (+# ww (word2Int# dt1)); 
      Both -> $wgo ys (+# ww (+# dt (word2Int# dt1))) 
     } 
     } 
    } 
end Rec } 

nào, trong hầu hết mọi trường hợp khi làm việc với cấp thấp, giải nén dữ liệu, là mục tiêu anyways, như hầu hết các chức năng của bạn là nhỏ và chi phí ít để nội tuyến.

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