2014-01-12 16 views
154

Ich habe Probleme, GHC auf eine Funktion mit einer Klassenbeschränkung zu spezialisieren. Ich habe hier ein minimales Beispiel für mein Problem: Foo.hs und Main.hs. Die beiden Dateien kompilieren (GHC 7.6.2, ghc -O3 Main) und ausführen.Spezialisierung mit Einschränkungen

HINWEIS: Foo.hs ist wirklich abgespeckt. Wenn Sie sehen möchten, warum die Einschränkung benötigt wird, können Sie ein wenig mehr Code here sehen. Wenn ich den Code in eine einzige Datei schreibe oder viele andere geringfügige Änderungen vornehme, leitet GHC den Anruf einfach an plusFastCyc. Dies wird im echten Code nicht vorkommen, weil plusFastCyc zu groß für GHC ist, um inline zu sein, selbst wenn es INLINE markiert wird. Der Punkt ist spezialisieren der Anruf an plusFastCyc, nicht inline es. plusFastCyc wird an vielen Stellen im echten Code aufgerufen, daher wäre das Duplizieren einer so großen Funktion nicht wünschenswert, selbst wenn ich GHC zwingen könnte, dies zu tun.

Der Code von Interesse ist die plusFastCyc in Foo.hs, hier wiedergegeben:

{-# 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 

Die Main.hs Datei hat zwei Treiber: vtTest, die in ~ 3 Sekunden läuft, und fcTest, die läuft ~ 83 Sekunden, wenn kompiliert mit -O3 unter Verwendung der forall 'd Spezialisierung.

Die core shows, die für den vtTest Test wird der Additionscode, um Unboxed Vektoren über Int s spezialisiert zu werden usw., während generic Vektorcode für fcTest verwendet. In Zeile 10 können Sie sehen, dass GHC eine spezielle Version von plusFastCyc im Vergleich zur generischen Version in Zeile 167 schreibt. Die Regel für die Spezialisierung ist in Zeile 225. Ich glaube, dass diese Regel in Zeile 270 ausgelöst werden sollte. (main6 ruft iterate main8 y, so main8 ist, wo plusFastCyc spezialisiert sein sollte.)

Mein Ziel ist es fcTest so schnell wie vtTest durch die Spezialisierung plusFastCyc zu machen. Ich habe zwei Wege gefunden, dies zu tun:

  1. explicity Anruf inline von GHC.Exts in fcTest.
  2. Entfernen Sie die Factored m Int Einschränkung auf plusFastCyc.

Option 1 ist unbefriedigend, weil in der eigentlichen Code-Basis plusFastCyc ein häufig verwendeter Betrieb ist und eine sehr große Funktion, so sollte es nicht bei jeder Benutzung inlined werden. Stattdessen sollte GHC eine spezielle Version von plusFastCyc aufrufen. Option 2 ist nicht wirklich eine Option, weil ich die Einschränkung im echten Code brauche.

Ich habe versucht eine Vielzahl von Optionen mit (und nicht mit) INLINE, INLINABLE und SPECIALIZE, aber nichts scheint zu funktionieren. (EDIT: Ich habe vielleicht zu viel von plusFastCyc abgestreift, um mein Beispiel klein zu machen, so INLINE könnte dazu führen, dass die Funktion inline. Dies passiert nicht in meinem echten Code, weil plusFastCyc so groß ist.) In diesem speziellen Beispiel bekomme ich keine match_co: needs more cases oder RULE: LHS too complicated to desugar (und here) Warnungen, obwohl ich viele match_co Warnungen erhielt, bevor ich das Beispiel minimierte. Vermutlich ist das "Problem" die Factored m Int Einschränkung in der Regel; Wenn ich Änderungen an dieser Nebenbedingung vornehme, läuft fcTest so schnell wie vtTest.

Mache ich etwas, das GHC einfach nicht mag? Warum wird GHC nicht die plusFastCyc spezialisieren, und wie kann ich es machen?

UPDATE

Das Problem in GHC bleibt 7.8.2, so ist diese Frage nach wie vor relevant.

+3

Ich habe gerade versucht, spezialisiert für ein * specific * 'm', nämlich' M'. Das hat den Job erledigt, aber ich kann mich nicht auf bestimmte Phantomtypen im realen Programm spezialisieren, da sie verdinglicht sind. – crockeea

+0

Ich habe auch einen GHC-Fehlerbericht https://ghc.haskell.org/trac/ghc/ticket/8668 eingereicht, aber das Problem ist noch offen. Der Fehlerberichterstattungsprozess hat mir geholfen, die Frage ein wenig aufzulockern, also wird es hoffentlich leichter sein, herauszufinden, was vor sich geht. – crockeea

+0

@monojohnny Sorry das zu hören, ich glaube du kannst es als solches kennzeichnen. Ich denke, ich fordere GHC auf, etwas Vernünftiges zu tun, und das wird es nicht tun. Ich bin mir nicht sicher, ob ich es falsch mache, oder ob dies eine Idiosynkrasie mit dem Compiler ist, die einen Workaround haben könnte. Ich habe Workarounds für Spezialisierung und Regeln in einer speziellen Bibliothek auf Hacker-Basis gesehen, die mir im Moment entgehen, also hoffe ich, dass jemand in der Community mit mehr GHC-Erfahrung als ich selbst wissen kann, wie man Spezialisierung erreicht. – crockeea

Antwort

4

GHC gibt auch eine Option SPECIALIZE eine Typ-Klasse-Instanz-Deklaration. Ich habe versucht, dies mit dem (Expanded) Code von Foo.hs, indem Sie die folgenden setzen:

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 

dies zu ändern, habe aber nicht die gewünschte Beschleunigung zu erreichen. Was diese Leistungsverbesserung erzielen tat, war manuell eine spezielle Instanz für den Typ VT U.Vector m Int mit den gleichen Funktionsdefinitionen hinzufügen, wie folgt:

instance (Factored m Int) => Num (VT U.Vector m Int) where 
    VT x + VT y = VT $ V.zipWith (+) x y 

Dies erfordert OverlappingInstances und FlexibleInstances in LANGUAGE hinzufügen.

Interessanterweise bleibt in dem Beispielprogramm die mit der überlappenden Instanz erzielte Beschleunigung erhalten, selbst wenn Sie alle SPECIALIZE und INLINABLE Pragma entfernen.

+0

Definitiv nicht optimal, aber es ist die erste Lösung, die das Ziel tatsächlich erreicht, also nehme ich an, dass ich es für jetzt nehme ... – crockeea