Специализация с ограничениями

У меня проблемы с GHC, чтобы специализировать функцию с ограничением класса. У меня есть минимальный пример моей проблемы: Foo.hs и Main.hs. Эти два файла компилируются (GHC 7.6.2, ghc -O3 Main) и запускаются.

Примечание: Foo.hs действительно удаляется. Если вы хотите узнать, почему требуется ограничение, вы можете увидеть здесь немного больше кода здесь. Если я помещу код в один файл или сделаю много других незначительных изменений, GHC просто вставляет вызов plusFastCyc. Это не произойдет в реальном коде, потому что plusFastCyc слишком велико для GHC для встроенного, даже если отмечено INLINE. Дело в том, чтобы специализировать вызов plusFastCyc, а не встраивать его. plusFastCyc вызывается во многих местах реального кода, поэтому дублирование такой большой функции было бы нежелательным, даже если бы я мог заставить GHC это сделать.

Код интереса - это plusFastCyc in Foo.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 есть два драйвера: vtTest, который длится ~ 3 секунды и fcTest, который работает в ~ 83 секунды при компиляции с -O3 с помощью forall 'd специализации.

Ядро показывает, что для теста vtTest код добавления специализируется на Unboxed векторах над Int s и т.д., в то время как для fcTest используется общий векторный код. В строке 10 вы можете видеть, что GHC пишет специализированную версию plusFastCyc по сравнению с общей версией в строке 167. Правило для специализации находится в строке 225. Я считаю, что это правило должно срабатывать по строке 270. (main6 вызывает iterate main8 y, поэтому main8 есть где plusFastCyc должен быть специализированным.)

Моя цель - сделать fcTest так же быстро, как vtTest, специализируясь на plusFastCyc. Я нашел два способа сделать это:

  • Объяснение вызова INLINE от GHC.Exts в fcTest.
  • Удалите ограничение Factored m Int на plusFastCyc.

Вариант 1 неудовлетворительный, поскольку в фактической кодовой базе plusFastCyc используется часто используемая операция и очень большая функция, поэтому она не должна быть встроена при каждом использовании. Скорее, GHC должен назвать специализированную версию plusFastCyc. Вариант 2 на самом деле не вариант, потому что мне нужно ограничение в реальном коде.

Я пробовал различные варианты, используя (и не используя) INLINE, INLINABLE и SPECIALIZE, но ничего не работает. (РЕДАКТИРОВАТЬ: я мог бы удалить слишком много из 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, поэтому этот вопрос по-прежнему имеет значение.

Ответ 1

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 прагмы.