GHCがクラス制約を持つ関数を特殊化するのに問題があります。ここに私の問題の最小限の例を示します: Foo.hs および Main.hs 。 2つのファイルがコンパイルされ(GHC 7.6.2、ghc -O3 Main
)、実行されます。
注:Foo.hs
は本当に削除されます。制約が必要な理由を確認したい場合は、もう少しコードを見ることができます here 。コードを1つのファイルに配置するか、他の多くの小さな変更を加えると、GHCはplusFastCyc
への呼び出しを単純にインライン化します。 plusFastCyc
は、INLINE
とマークされていても、GHCがインラインするには大きすぎるため、実際のコードでは発生しません。ポイントは、plusFastCyc
への呼び出しをインライン化せずにspecializeすることです。 plusFastCyc
は実際のコードの多くの場所で呼び出されるため、GHCに強制することができたとしても、このような大きな関数を複製することは望ましくありません。
対象のコードは、Foo.hs
のplusFastCyc
で、ここに再現されています。
{-# INLINEABLE plusFastCyc #-}
{-# SPECIALIZE plusFastCyc ::
forall m . (Factored m Int) =>
(FastCyc (VT U.Vector m) Int) ->
(FastCyc (VT U.Vector m) Int) ->
(FastCyc (VT U.Vector m) Int) #-}
-- Although the next specialization makes `fcTest` fast,
-- it isn't useful to me in my real program because the phantom type M is reified
-- {-# SPECIALIZE plusFastCyc ::
-- FastCyc (VT U.Vector M) Int ->
-- FastCyc (VT U.Vector M) Int ->
-- FastCyc (VT U.Vector M) Int #-}
plusFastCyc :: (Num (t r)) => (FastCyc t r) -> (FastCyc t r) -> (FastCyc t r)
plusFastCyc (PowBasis v1) (PowBasis v2) = PowBasis $ v1 + v2
Main.hs
ファイルには2つのドライバーがあります。vtTest
(3秒以内に実行)とfcTest
(_O3でforall
'd特殊化を使用してコンパイルした場合に〜83秒以内に実行)。
コアが示すvtTest
テストの場合、追加コードはUnboxed
sなどのInt
ベクトルに特化されているのに対し、fcTest
には汎用ベクトルコードが使用されます。 10行目では、GHCが167行目の汎用バージョンと比較して、plusFastCyc
の特殊バージョンを記述していることがわかります。特殊化のルールは225行目です。このルールは270行目で起動するはずです。(main6
はiterate main8 y
を呼び出すため、main8
はplusFastCyc
を特化する必要があります。)
私の目標は、fcTest
を特殊化して、vtTest
と同じくらい速くplusFastCyc
にすることです。これを行うには2つの方法があります。
inline
のGHC.Exts
からfcTest
を明示的に呼び出します。plusFastCyc
のFactored m Int
制約を削除します。オプション1は、実際のコードベースではplusFastCyc
が頻繁に使用される操作であり、very大規模な関数であるため、使用するたびにインライン化しないでください。むしろ、GHCはplusFastCyc
の特殊バージョンを呼び出す必要があります。実際のコードには制約が必要なので、オプション2は実際にはオプションではありません。
INLINE
、INLINABLE
、およびSPECIALIZE
を使用する(使用しない)さまざまなオプションを試しましたが、何も機能しないようです。 ([〜#〜] edit [〜#〜]:サンプルを小さくするためにplusFastCyc
を削除しすぎたため、INLINE
が発生する可能性がありますインライン化される関数。plusFastCyc
が非常に大きいため、これは実際のコードでは発生しません。)この特定の例では、 match_co: needs more cases
または RULE: LHS too complicated to desugar
(および ここ )警告。ただし、例を最小化する前に多くのmatch_co
警告を受け取っていました。おそらく、「問題」はルールのFactored m Int
制約です。その制約を変更すると、fcTest
はvtTest
と同じ速さで実行されます。
GHCが気に入らないことをやっていますか? GHCがplusFastCyc
を特殊化しないのはなぜですか?
[〜#〜] update [〜#〜]
この問題はGHC 7.8.2でも続いているため、この質問は依然として関連しています。
GHCは、型クラスのインスタンス宣言SPECIALIZE
へのオプションも提供します。私はこれをFoo.hs
の(拡張された)コードで試し、以下を追加しました:
instance (Num r, V.Vector v r, Factored m r) => Num (VT v m r) where
{-# SPECIALIZE instance ( Factored m Int => Num (VT U.Vector m Int)) #-}
VT x + VT y = VT $ V.zipWith (+) x y
ただし、この変更では目的の高速化は達成されませんでした。このパフォーマンスの改善を達成したのは、次のように、同じ関数定義を持つVT U.Vector m Int
型の特殊なインスタンスを追加する手動でした。
instance (Factored m Int) => Num (VT U.Vector m Int) where
VT x + VT y = VT $ V.zipWith (+) x y
これには、OverlappingInstances
にFlexibleInstances
とLANGUAGE
を追加する必要があります。
興味深いことに、サンプルプログラムでは、すべてのSPECIALIZE
およびINLINABLE
プラグマを削除しても、オーバーラップするインスタンスで得られる高速化は維持されます。