У меня есть эти основные типы в моем коде:
newtype Foo (m :: Factored) = Foo Int64 deriving (NFData)
foo :: forall m . (Fact m) => Foo m -> Foo m
class T t where t :: (Fact m ) => t m -> t m
instance T Foo where t = foo
newtype Bar t (m :: Factored) = Bar (t m) deriving (NFData)
bar :: (Fact m, T t) => Bar t m -> Bar t m
bar (Bar v) = Bar $ t v
(игнорировать Fact
и Factored
на данный момент). Я сравниваю код на разных уровнях, сравнивая производительность foo
, t
и bar
. В тестах t = foo
и bar
применяется только t
через newtype
. Таким образом, их время выполнения должно быть практически идентичным, но criterion сообщает, что foo
принимает 9.2ns, t
занимает примерно вдвое больше, чем при 17.45ns, и bar
берет колоссальные 268.1ns.
Я экспериментировал с добавлением INLINABLE
и даже SPECIALIZE
прагмы, но они не помогают. Я хочу верить, что GHC имеет некоторый магический синтаксис/оптимизацию/и т.д., Который может быть последовательно применен для решения этих проблем с производительностью. Например, я видел случаи, когда пишущий код в бесшумном стиле имеет значительные последствия для производительности.
Полный код можно найти здесь. Я обещаю, что это не пугает. Модули:
- Foo: определяет
foo
,foo
иt
- Бар: определяет
bar
иbar
- FooBench: определяет ориентир для
foo
- TBench: определяет ориентир для
t
- BarBench: определяет ориентир для
bar
- Main: выполняется три теста
- Factored: определяет
Fact
иFactored
с помощью singletons
Большинство модулей являются крошечными; Я определил три теста в отдельных файлах, чтобы я мог исследовать их ядро. Я создал ядро для трех модулей *Bench
и выровнял их, насколько мог. Они всего ~ 250 строк каждый, а первые 200 строк идентичны, вплоть до переименования. Проблема в том, что я не знаю, что делать из последних 50 или около того строк. Здесь (core) diff для FooBench
vs TBench
находится здесь, diff для TBench
vs BarBench
- здесь, а diff для FooBench
vs BarBench
здесь.
Несколько вопросов, которые у меня есть:
-
На высоком уровне, какова существенная разница между основными файлами? Я ищу что-то вроде "Здесь вы можете видеть, что GHC не вписывает вызов
x
". Что я должен искать? -
Что можно сделать, чтобы все три теста все работали примерно в 9.2ns? Оптимизация GHC?
INLINE
/INLINABLE
прагмы?SPECIALIZE
прагмы, которые я пропустил? (Вам не разрешено специализироваться наF128::Factored
, в моей реальной библиотеке это значение может быть подтверждено во время выполнения.) Ограничение/задержка встраивания в конкретную фазу?
Хотя я ищу фактическое решение для быстрого тестирования тестов, возможно, что трюки, которые работают для этого примера, не будут масштабироваться в моей реальной библиотеке. В результате я также ищу "высокоуровневое" объяснение того, почему конкретная техника должна работать.