У меня есть эти основные типы в моем коде:
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, в моей реальной библиотеке это значение может быть подтверждено во время выполнения.) Ограничение/задержка встраивания в конкретную фазу?
Хотя я ищу фактическое решение для быстрого тестирования тестов, возможно, что трюки, которые работают для этого примера, не будут масштабироваться в моей реальной библиотеке. В результате я также ищу "высокоуровневое" объяснение того, почему конкретная техника должна работать.