Я задаю этот вопрос со ссылкой на этот ТАК вопрос.
Принятый ответ от Дона Стюарта: Первая строка гласит: «Ваш код очень полиморфный, измените все переменные с плавающей запятой на Double…», и это увеличит производительность в 4 раза.
Я заинтересован в выполнении матричных вычислений в Haskell. Должен ли я сделать это привычкой к написанию мономорфного кода?
Но некоторые языки хорошо используют специальный полиморфизм для генерации быстрого кода, почему GHC не может или не может? (читайте C ++ или D)
почему мы не можем иметь что-то вроде blitz ++ или eigen для Haskell? Я не понимаю, как работают классы типов и (ad-hoc) полиморфизм в GHC.
Я не понимаю, как работают классы типов в GHC.
Хорошо, рассмотрим эту функцию:
linear :: Num x => x -> x -> x -> x
linear a b x = a*x + b
Это принимает три числа в качестве ввода и возвращает число в качестве вывода. Эта функция принимает любой тип номера; это полиморфно. Как GHC реализует это? По сути, компилятор создает «словарь классов», в котором содержатся все методы класса (в данном случае +
, -
, *
и т. д.) Этот словарь становится дополнительным скрытым аргументом функции. Что-то вроде этого:
data NumDict x =
NumDict
{
method_add :: x -> x -> x,
method_subtract :: x -> x -> x,
method_multiply :: x -> x -> x,
...
}
linear :: NumDict x -> x -> x -> x -> x
linear dict a b x = a `method_multiply dict` x `method_add dict` b
Всякий раз, когда вам вызов функция, компилятор автоматически вставляет правильный словарь — если вызывающая функция не также полиморфный, в этом случае он получит сам словарь, так что просто передайте его.
По правде говоря, функции, в которых отсутствует полиморфизм, обычно быстрее не столько из-за отсутствия поиска функций, сколько потому, что знание типов позволяет выполнять дополнительную оптимизацию. Например, наш полиморфный linear
функция будет работать с числами, векторами, матрицами, отношениями, комплексными числами, что-нибудь. Теперь, если компилятор знает, что мы хотим использовать его, скажем, Double
теперь все операции становятся единичными инструкциями машинного кода, все операнды могут передаваться в регистры процессора и т. д. Все это приводит к фантастически эффективному коду. Даже если это сложные числа с Double
компоненты, мы можем сделать это красиво и эффективно. Если мы не знаем, какой тип получится, мы не сможем выполнить какую-либо из этих оптимизаций … Вот откуда обычно происходит большая разница в скорости.
Для крошечной функции, такой как linear, весьма вероятно, что она будет вставлена при каждом вызове, что не приведет к дополнительным затратам на полиморфизм и небольшому количеству дублирования кода — скорее как шаблон C ++. Для большей, более сложной полиморфной функции может потребоваться некоторая стоимость. В общем, это решает компилятор, а не вы — если только вы не хотите начать разбрасывать прагмы по всему месту. 😉 Или, если вы на самом деле не используете какой-либо полиморфизм, вы можете просто дать все сигнатуры мономорфного типа …
В случае полиморфного кода обычно существует компромисс между размером кода и скоростью кода. Либо вы создаете отдельную версию одного и того же кода для каждого типа, с которым он будет работать, что приводит к увеличению размера кода, либо вы создаете одну версию, которая может работать с несколькими типами, что будет медленнее.
Реализации шаблонов на C ++ делают выбор в пользу увеличения скорости кода за счет увеличения размера кода. По умолчанию GHC принимает противоположный компромисс. Однако можно заставить GHC создавать отдельные версии для разных типов, используя прагмы SPECIALIZE и INLINABLE. Это приведет к полиморфному коду, скорость которого аналогична мономорфному коду.
Я хочу дополнить ответ Дирка, сказав, что INLINABLE
обычно рекомендуется более SPECIALIZE
, INLINABLE
аннотация к функции гарантирует, что модуль экспортирует исходный код функции, чтобы она могла быть специализированной в момент использования. Это обычно устраняет необходимость предоставления отдельных SPECIALIZE
Прагмы для каждого случая использования.
В отличие от INLINE
, INLINABLE
не меняет эвристику оптимизации GHC. Он просто говорит «Пожалуйста, экспортируйте исходный код».