GHCにおける自動特殊化の推移性 質問する

GHCにおける自動特殊化の推移性 質問する

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

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

そして

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

GHCは自動的に特殊化されるはずだ一部/ほとんど/すべて(?)関数はプラグマINLINABLE なしでマークされており、明示的なプラグマを使用すると、特殊化は推移的になります。私の質問は、auto特殊化は推移的であるかどうかです。

具体的には、次のような小さな例があります。

メイン.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が、のインスタンスを特殊化しないため、パフォーマンスが低下します。(+)Qux Num

しかし、明示的なプラグマ

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

ドキュメントに示されているように推移的特殊化が行われるため、 は(+)特殊化され、コードは 30 倍高速になります (両方とも でコンパイル)。これは想定された動作ですか?明示的なプラグマを使用して推移的に特殊化されること-O2のみを想定する必要がありますか?(+)


アップデート

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

ベストアンサー1

短い答え:

私が理解する限り、この質問の要点は次のとおりです。

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

私の知る限り、答えは「いいえ」、「ほとんどははいだが他の手段もあります」、「いいえ」です。

コードのインライン化と型アプリケーションの特殊化は、速度 (実行時間) とコード サイズの間のトレードオフです。デフォルト レベルでは、コードが肥大化することなく、ある程度の速度向上が得られます。より徹底的なレベルを選択するかどうかは、SPECIALISEプラグマを介してプログラマーの裁量に委ねられます。

説明:

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

が、型クラス によって制約されるf型変数を含む型を持つ関数であるとします。GHC は、(a) 同じモジュール内の任意の関数 のソースコードで がその型適用で呼び出された場合、または (b) が とマークされている場合、 からインポートする他の任意のモジュール のソースコードで が型適用で呼び出された場合デフォルトで の型適用に関して特殊化します ( を置き換えます)。したがって、自動特殊化は推移的ではなく、のソースコードでインポートされ、 が呼び出される関数にのみ影響します。aC afatffINLINABLE fBINLINABLEA

この例では、 のインスタンスをNum次のように書き換えます。

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

quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
  • quxAddは によって特にインポートされませんMainMainは のインスタンス辞書をインポートしNum (Qux Int)、この辞書quxAddには のレコードが含まれます(+)。ただし、辞書はインポートされますが、辞書で使用される内容はインポートされません。
  • plusは を呼び出さないので、のインスタンス辞書内のレコードquxAddに格納されている関数を使用します。この辞書は、コンパイラによって呼び出しサイト ( 内) で設定されます。(+)Num tMain

おすすめ記事