web-dev-qa-db-ja.com

GHCにおける自動専門化の推移性

GHC 7.6の ドキュメント から:

[Y]多くの場合、最初からSPECIALIZEプラグマさえ必要ありません。モジュールMをコンパイルするとき、GHCのオプティマイザー(-Oを使用)は、Mで宣言された各トップレベルのオーバーロード関数を自動的に考慮し、Mで呼び出されるさまざまな型に特化します。オプティマイザーは、インポートされた各INLINABLEオーバーロード関数も考慮し、 Mで呼び出されるさまざまなタイプに特化しています。

そして

さらに、関数fのSPECIALIZEプラグマが与えられると、GHCは、SPECIALIZEプラグマと同じモジュール内にある場合、またはINLINABLEである場合、fによって呼び出される型クラスがオーバーロードされた関数の特殊化を自動的に作成します。など、推移的に。

したがって、GHCは自動的に特化する必要があります some/most/all(?)INLINABLEとマークされた関数withoutプラグマです。明示的なプラグマを使用する場合、特化は推移的です。私の質問は:auto-specializationは推移的ですか?

具体的には、小さな例を次に示します。

Main.hs:

import Data.Vector.Unboxed as U
import Foo

main =
    let y = Bar $ Qux $ U.replicate 11221184 0 :: Foo (Qux Int)
        (Bar (Qux ans)) = iterate (plus y) y !! 100
    in putStr $ show $ foldl1' (*) ans

Foo.hs:

module Foo (Qux(..), Foo(..), plus) where

import Data.Vector.Unboxed as U

newtype Qux r = Qux (Vector r)
-- GHC inlines `plus` if I remove the bangs or the Baz constructor
data Foo t = Bar !t
           | Baz !t

instance (Num r, Unbox r) => Num (Qux r) where
    {-# INLINABLE (+) #-}
    (Qux x) + (Qux y) = Qux $ U.zipWith (+) x y

{-# INLINABLE plus #-}
plus :: (Num t) => (Foo t) -> (Foo t) -> (Foo t)
plus (Bar v1) (Bar v2) = Bar $ v1 + v2

GHCはplusの呼び出しを専門にしていますが、パフォーマンスを殺すQuxNumインスタンスでnot特殊化(+)を行います。

ただし、明示的なプラグマ

{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}

ドキュメントが示すようにtransitive特殊化になります。したがって、(+)は特殊化され、コードは30倍高速になります(両方とも-O2でコンパイルされます)。これは予想される動作ですか? (+)が明示的なプラグマで推移的に特殊化されることのみを期待すべきですか?


UPDATE

7.8.2のドキュメントは変更されておらず、動作も同じであるため、この質問は依然として関連しています。

390
crockeea

短い答え:

私が理解しているように、質問のキーポイントは次のとおりです。

  • 「自動特殊化は推移的ですか?」
  • 明示的なプラグマを使用して(+)を推移的に特化することのみを期待すべきですか?
  • (明らかに意図されている)これはGHCのバグですか?ドキュメントと矛盾していますか?

私の知る限り、答えはいいえ、ほとんどはいですが、他の方法があり、いいえ.

コードのインライン化と型アプリケーションの特殊化は、速度(実行時間)とコードサイズのトレードオフです。デフォルトのレベルでは、コードを肥大化させることなく速度が向上します。より包括的なレベルを選択することは、SPECIALISEname__プラグマを介してプログラマーの裁量に任されています。

説明:

オプティマイザーは、インポートされた各INLINABLEオーバーロード関数も考慮し、Mで呼び出されるさまざまなタイプに特化します。

fname__は、型クラスC aによって制約される型変数aname__を含む型の関数であるとします。 GHCは、デフォルトでfname__を(aname__をtname__に置き換えて)型アプリケーションに関して、(a)同じモジュール内の任意の関数のソースコードでfname__がその型アプリケーションで呼び出される場合、または(b)fname__がINLINABLEname__としてマークされている場合、次に、importsfname__からのBname__である他のモジュール。したがって、自動特殊化は推移的ではなく、インポートされ、INLINABLEname__のソースコード内で呼び出されるAname__関数にのみ影響します。

あなたの例では、次のようにNumname__のインスタンスを書き換えた場合:

instance (Num r, Unbox r) => Num (Qux r) where
    (+) = quxAdd

quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
  • quxAddname__は、Mainname__によって具体的にインポートされません。 Mainname__はNum (Qux Int)のインスタンス辞書をインポートし、この辞書には(+)のレコードにquxAddname__が含まれます。ただし、辞書はインポートされますが、辞書で使用されるコンテンツはインポートされません。
  • plusname__はquxAddname__を呼び出さず、(+)のインスタンスディクショナリのNum tレコードに保存されている関数を使用します。この辞書は、コンパイラーによって(Mainname__の)呼び出しサイトで設定されます。