web-dev-qa-db-ja.com

制約付きの専門化

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.hsplusFastCycで、ここに再現されています。

{-# 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テストの場合、追加コードはUnboxedsなどのIntベクトルに特化されているのに対し、fcTestには汎用ベクトルコードが使用されます。 10行目では、GHCが167行目の汎用バージョンと比較して、plusFastCycの特殊バージョンを記述していることがわかります。特殊化のルールは225行目です。このルールは270行目で起動するはずです。(main6iterate main8 yを呼び出すため、main8plusFastCycを特化する必要があります。)

私の目標は、fcTestを特殊化して、vtTestと同じくらい速くplusFastCycにすることです。これを行うには2つの方法があります。

  1. inlineGHC.ExtsからfcTestを明示的に呼び出します。
  2. plusFastCycFactored m Int制約を削除します。

オプション1は、実際のコードベースではplusFastCycが頻繁に使用される操作であり、very大規模な関数であるため、使用するたびにインライン化しないでください。むしろ、GHCはplusFastCycの特殊バージョンを呼び出す必要があります。実際のコードには制約が必要なので、オプション2は実際にはオプションではありません。

INLINEINLINABLE、およびSPECIALIZEを使用する(使用しない)さまざまなオプションを試しましたが、何も機能しないようです。 ([〜#〜] edit [〜#〜]:サンプルを小さくするためにplusFastCycを削除しすぎたため、INLINEが発生する可能性がありますインライン化される関数。plusFastCycが非常に大きいため、これは実際のコードでは発生しません。)この特定の例では、 match_co: needs more cases または RULE: LHS too complicated to desugar (および ここ )警告。ただし、例を最小化する前に多くのmatch_co警告を受け取っていました。おそらく、「問題」はルールのFactored m Int制約です。その制約を変更すると、fcTestvtTestと同じ速さで実行されます。

GHCが気に入らないことをやっていますか? GHCがplusFastCycを特殊化しないのはなぜですか?

[〜#〜] update [〜#〜]

この問題はGHC 7.8.2でも続いているため、この質問は依然として関連しています。

154
crockeea

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

これには、OverlappingInstancesFlexibleInstancesLANGUAGEを追加する必要があります。

興味深いことに、サンプルプログラムでは、すべてのSPECIALIZEおよびINLINABLEプラグマを削除しても、オーバーラップするインスタンスで得られる高速化は維持されます。