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
の呼び出しを専門にしていますが、パフォーマンスを殺すQux
Num
インスタンスでnot特殊化(+)
を行います。
ただし、明示的なプラグマ
{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}
ドキュメントが示すようにtransitive特殊化になります。したがって、(+)
は特殊化され、コードは30倍高速になります(両方とも-O2
でコンパイルされます)。これは予想される動作ですか? (+)
が明示的なプラグマで推移的に特殊化されることのみを期待すべきですか?
UPDATE
7.8.2のドキュメントは変更されておらず、動作も同じであるため、この質問は依然として関連しています。
私が理解しているように、質問のキーポイントは次のとおりです。
- 「自動特殊化は推移的ですか?」
- 明示的なプラグマを使用して(+)を推移的に特化することのみを期待すべきですか?
- (明らかに意図されている)これはGHCのバグですか?ドキュメントと矛盾していますか?
私の知る限り、答えはいいえ、ほとんどはいですが、他の方法があり、いいえ.
コードのインライン化と型アプリケーションの特殊化は、速度(実行時間)とコードサイズのトレードオフです。デフォルトのレベルでは、コードを肥大化させることなく速度が向上します。より包括的なレベルを選択することは、SPECIALISE
name__プラグマを介してプログラマーの裁量に任されています。
オプティマイザーは、インポートされた各INLINABLEオーバーロード関数も考慮し、Mで呼び出されるさまざまなタイプに特化します。
f
name__は、型クラスC a
によって制約される型変数a
name__を含む型の関数であるとします。 GHCは、デフォルトでf
name__を(a
name__をt
name__に置き換えて)型アプリケーションに関して、(a)同じモジュール内の任意の関数のソースコードでf
name__がその型アプリケーションで呼び出される場合、または(b)f
name__がINLINABLE
name__としてマークされている場合、次に、importsf
name__からのB
name__である他のモジュール。したがって、自動特殊化は推移的ではなく、インポートされ、INLINABLE
name__のソースコード内で呼び出されるA
name__関数にのみ影響します。
あなたの例では、次のようにNum
name__のインスタンスを書き換えた場合:
instance (Num r, Unbox r) => Num (Qux r) where
(+) = quxAdd
quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
quxAdd
name__は、Main
name__によって具体的にインポートされません。 Main
name__はNum (Qux Int)
のインスタンス辞書をインポートし、この辞書には(+)
のレコードにquxAdd
name__が含まれます。ただし、辞書はインポートされますが、辞書で使用されるコンテンツはインポートされません。plus
name__はquxAdd
name__を呼び出さず、(+)
のインスタンスディクショナリのNum t
レコードに保存されている関数を使用します。この辞書は、コンパイラーによって(Main
name__の)呼び出しサイトで設定されます。