Как надежно влиять на сгенерированный код на уровне, близком к машинному, используя GHC? - PullRequest
0 голосов
/ 30 августа 2018

Хотя это может звучать как теоретический вопрос, предположим, что я решил инвестировать и создать критически важное приложение, написанное на Haskell. Год спустя я обнаружил, что мне абсолютно необходимо улучшить производительность некоторого очень тонкого узкого места, и для этого потребуется оптимизировать доступ к памяти, близкий к возможностям сырой машины.

Некоторые предположения:

  • Это не система реального времени - случайные пики задержки допустимы (из-за прерываний, нарушений планирования потоков, случайных сборок мусора и т. Д.)
  • Это не числовая проблема - макет данных и шаблоны доступа к кешу наиболее важны (избегание погони за указателем, уменьшение условных переходов и т. Д.)
  • Код может быть привязан к конкретной версии GHC (но без разветвления)
  • Требуемая производительность требует изменения на месте предварительно выделенных массивов внеплановой памяти с учетом выравнивания (строки C, битовые поля и т. Д.)
  • Данные статически ограничены в массивах, и выделения редко, если когда-либо необходимы

Какие механизмы предлагает GHC для осуществления такого рода оптимизации? Надежно говоря, я имею в виду, что если изменение исходного кода приводит к тому, что код перестает работать, оно исправимо в исходном коде без перезаписи его в сборке.

  • Возможно ли уже использовать специфичные для GHC расширения и библиотеки?
  • Поможет ли пользовательский FFI избежать накладных расходов на соглашение о вызовах C?
  • Может ли плагин компилятора специального назначения сделать это через ограниченный исходный DSL?
  • Может ли генератор исходного кода из сборки высокого уровня (LLVM?) Быть решением?

1 Ответ

0 голосов
/ 31 августа 2018

Похоже, вы ищете неупакованные массивы. «unboxed» в значении haskell-land означает «не имеет представления кучи во время выполнения». Обычно вы можете узнать, скомпилирована ли какая-то часть вашего кода в распакованный цикл (цикл, который не выполняет выделения), скажем, взглянув на представление core (это очень похожий на haskell язык, это первый этап в компиляции). Так, например вы можете увидеть Int# в выводе ядра, что означает целое число, которое не имеет представления кучи (оно будет в регистре).

При оптимизации кода на Haskell мы регулярно смотрим на ядро ​​и ожидаем, что сможем манипулировать или корректировать регрессии производительности, изменяя исходный код (например, добавляя аннотацию строгости или перебирая функцию, чтобы она могла быть встроенной). Это не всегда весело, но будет довольно стабильно, особенно если вы закрепляете версию своего компилятора.

Назад к неупакованным массивам: GHC предоставляет множество низкоуровневых примопов в GHC.Prim, в частности, это звучит так, как будто вам нужны изменяемые распакованные массивы (MutableByteArray). Пакет primitive предоставляет эти праймеры за немного более безопасный и дружественный API и именно то, что вам следует использовать (и зависит от того, пишете ли вы свою собственную библиотеку).

Есть много других библиотек, которые реализуют распакованные массивы, такие как vector, и которые построены на MutableByteArray, но дело в том, что операции с этой структурой не генерируют мусор и, вероятно, компилируются в довольно предсказуемые машинные инструкции.

Вы также можете проверить эту технику , если вы выполняете числовую работу и хотите использовать определенную инструкцию или реализовать какой-то цикл непосредственно в сборке.

GHC также имеет очень мощный FFI, и вы можете изучить, как писать части вашей программы на C и взаимодействовать; Для этой цели haskell поддерживает закрепленные массивы среди других структур.

Если вам нужен больший контроль, чем те, что дают, то, скорее всего, haskell неправильный язык. Из вашего описания невозможно определить, соответствует ли это вашей проблеме (ваши требования кажутся противоречивыми: вам нужно уметь писать тщательно настроенный алгоритм кэширования, но с произвольными паузами в GC все в порядке?).

Последнее замечание: вы не можете полагаться на собственный генератор кода GHC для выполнения какой-либо из низкоуровневых оптимизаций снижения прочности, например, GCC выполняет (NCG GHC, вероятно, никогда не узнает о взломах, авто-векторизации и т. Д. И т. Д.). Вместо этого вы можете попробовать бэкэнд LLVM, но то, что вы видите ускорение в вашей программе, отнюдь не гарантировано.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...