1. ホーム
  2. haskell

[解決済み] 制約条件付き特殊化

2022-04-26 23:13:40

質問

GHCがクラス制約を持つ関数を特殊化するのに問題があります。ここに私の問題の最小限の例があります。 Foo.hs Main.hs . この2つのファイルは、コンパイル(GHC 7.6.2。 ghc -O3 Main ) と実行されます。

注意事項 Foo.hs は、実にシンプルなものです。なぜ制約が必要なのかを知りたい場合は、もう少しコードを見ることができます。 ここで . コードを1つのファイルにまとめたり、その他多くの細かい変更を加えると、GHCは単純にインラインで plusFastCyc . 実際のコードでは、このようなことは起こりません。 plusFastCyc とマークされていても、GHCがインライン化するには大きすぎる。 INLINE . ポイントは 特化 を呼び出すと plusFastCyc であり、インライン化されていない。 plusFastCyc は実際のコードでは多くの場所で呼び出されているので、このような大きな関数を重複させることは、たとえGHCに強制できたとしても好ましいことではありません。

注目のコードは plusFastCycFoo.hs を、ここに再現します。

{-# 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 でコンパイルした場合、 ~83 秒で実行されます。 forall 'dの特殊化を行う。

コアは というのは vtTest テストでは、追加されたコードは Unboxed の上のベクトル Int などがあり、汎用ベクトルコードは fcTest . 10 行目では、GHC が特別なバージョンの plusFastCyc 167行目の一般的なバージョンと比較して。 特殊化のためのルールは 225 行目にあります。私は、このルールは270行目で発火するべきだと考えています。( main6 コール iterate main8 y ということで main8plusFastCyc が特化されているはずです)。

私が目指しているのは fcTest と同じ速さで vtTest 特化することで plusFastCyc . 2つの方法を見つけました。

  1. 明示的な呼び出し inline から GHC.ExtsfcTest .
  2. を削除します。 Factored m Int の制約を受けます。 plusFastCyc .

オプション1は実際のコードベースでは不満足です。 plusFastCyc は頻繁に使用される操作であり 非常に 大きな関数なので、使用するたびにインライン化するべきではありません。むしろ、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 そして、どうすればいいのでしょうか?

アップデイト

この問題は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

これには OverlappingInstancesFlexibleInstancesLANGUAGE .

興味深いことに、このサンプルプログラムでは、オーバーラップインスタンスで得られた高速化は、すべての SPECIALIZEINLINABLE プラグマ