почему полиморфизм так дорог в Хаскеле (GHC)?

Я задаю этот вопрос со ссылкой на этот ТАК вопрос.
Принятый ответ от Дона Стюарта: Первая строка гласит: «Ваш код очень полиморфный, измените все переменные с плавающей запятой на Double…», и это увеличит производительность в 4 раза.

Я заинтересован в выполнении матричных вычислений в Haskell. Должен ли я сделать это привычкой к написанию мономорфного кода?

Но некоторые языки хорошо используют специальный полиморфизм для генерации быстрого кода, почему GHC не может или не может? (читайте C ++ или D)

почему мы не можем иметь что-то вроде blitz ++ или eigen для Haskell? Я не понимаю, как работают классы типов и (ad-hoc) полиморфизм в GHC.

19

Решение

Я не понимаю, как работают классы типов в 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 ++. Для большей, более сложной полиморфной функции может потребоваться некоторая стоимость. В общем, это решает компилятор, а не вы — если только вы не хотите начать разбрасывать прагмы по всему месту. 😉 Или, если вы на самом деле не используете какой-либо полиморфизм, вы можете просто дать все сигнатуры мономорфного типа …

11

Другие решения

В случае полиморфного кода обычно существует компромисс между размером кода и скоростью кода. Либо вы создаете отдельную версию одного и того же кода для каждого типа, с которым он будет работать, что приводит к увеличению размера кода, либо вы создаете одну версию, которая может работать с несколькими типами, что будет медленнее.

Реализации шаблонов на C ++ делают выбор в пользу увеличения скорости кода за счет увеличения размера кода. По умолчанию GHC принимает противоположный компромисс. Однако можно заставить GHC создавать отдельные версии для разных типов, используя прагмы SPECIALIZE и INLINABLE. Это приведет к полиморфному коду, скорость которого аналогична мономорфному коду.

18

Я хочу дополнить ответ Дирка, сказав, что INLINABLE обычно рекомендуется более SPECIALIZE, INLINABLE аннотация к функции гарантирует, что модуль экспортирует исходный код функции, чтобы она могла быть специализированной в момент использования. Это обычно устраняет необходимость предоставления отдельных SPECIALIZE Прагмы для каждого случая использования.

В отличие от INLINE, INLINABLE не меняет эвристику оптимизации GHC. Он просто говорит «Пожалуйста, экспортируйте исходный код».

17
По вопросам рекламы [email protected]