2015-04-24 2 views
10

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

Теперь, так как Haskell является чисто непреложным языком, вы, естественно, не можете перезаписывать существующие блоки памяти, что потенциально делают такие вещи, как foldl гораздо медленнее, чем for цикла с непрерывно доступом к переменному результату будет в С.

Haskell делает что-то внутренне, чтобы смягчить эту потерю производительности? И вообще, каковы его свойства относительно местности?

+5

Конечно, Haskell не указывает это - так что это будет зависеть от реализации (скорее всего, GHC), и я думаю, что это будет * smart * достаточно, чтобы скомпилировать что-то вроде 'foldl' в цикл (если не сам GHC, бэкэнд справится), но я действительно просто угадываю - * конечно * вы всегда можете попробовать это для себя и посмотреть на выход;) – Carsten

+2

Читает все еще пользу от локальности. Мутируемые массивы в подходящих монадах должны иметь эквивалентную производительность, как на императивных языках. Неизменяемые структуры данных, конечно, не позволяют простоту модификации на месте. В некоторых случаях GHC может оптимизировать это (например, жесткие числовые циклы не выделяют новые целые числа на каждой итерации). OTOH, имеющий неизменность, очень помогает в распараллеливании кода без частых аннулирования кеша. – chi

ответ

9

Общее правило заключается в том, что для программирования «ванильного» Haskell вы получаете очень мало (если есть) контроль над макетом памяти и местностью памяти.

Однако существует множество дополнительных функций, которые позволяют осуществлять такой контроль, и библиотеки, которые открывают для них дружественные абстракции. Библиотека vector, вероятно, самая популярная из последних. Эта библиотека предоставляет несколько типов массивов фиксированного размера, два из которых (Data.Vector.Unboxed и Data.Vector.Storable) предоставляют вам местоположение данных, представляя векторы и их содержимое как непрерывные массивы памяти. Data.Vector.Unboxed даже содержит простую автоматическую трансформацию «структуры массивов» - распакованный вектор пар будет представлен как пара нераспознанных векторов, по одному для каждой из парных компонентов.

Другим примером является библиотека JuicyPixels для обработки изображений, которая представляет изображения в памяти как непрерывные растровые изображения. Это фактически заканчивается до Data.Vector.Storable, в котором используется стандартный объект (Foreign.Storable) для перевода пользовательских типов данных Haskell в и из необработанных байтов.

Но общий шаблон таков: в Haskell, когда вас интересует местность памяти, вы определяете, какие данные должны извлечь выгоду из этого и объединить его вместе в пользовательский тип данных, реализация которого была разработана для обеспечения локальности и производительности гарантии. Написание такого типа данных является передовой задачей, но большая часть работы была сделана уже в многоразовой манере (например, это означает, что JuicyPixels в основном просто повторно использует vector).

Следует также отметить, что:

  1. vector обеспечивает STREAM Fusion оптимизаций для устранения промежуточных массивов при применении вложенных векторных преобразований. Если вы создаете вектор от 0 до 1 000 000, отфильтруйте четные числа, сопоставьте функцию (^2) над этим и суммируйте элементы результата, ни один массив не будет выделен - библиотека имеет умение переписывать это в цикл аккумулятора от 0 до 1 000 000. Таким образом, вектор foldl не обязательно медленнее, чем цикл for - не может быть никакого массива!
  2. vector также предоставляет изменяемые массивы. В более общем плане, в Haskell вы можете перезаписать существующую память, если вы действительно настаиваете. Это просто (а) не парадигма по умолчанию в языке, и поэтому (б) немного неуклюжий, но абсолютно послушный, если вам просто нужно это в нескольких чувствительных к производительности местах.

Поэтому большую часть времени ответ на вопрос «Я хочу местонахождение памяти» используется «vector».

+0

в отношении вашего потокового слияния все равно нужно будет хранить данные в памяти, так как вы не можете изменить что-то, чего нет. –

+0

. В тщательно подобранных операциях в примере слияния потоков требуется не более одного элемента «вектор» за раз, и все данные будут находиться в регистре или, возможно, в стеке. Конечно, вы могли бы просто написать явный цикл самостоятельно; потоковое слияние - не волшебство. –

+0

Кстати, пакет массивов, поставляемый с GHC, также включает распакованные и сохраняемые массивы. –

9

Haskell - это чрезвычайно высокоуровневый язык, и вы задаете вопрос о чрезвычайно низкой детализации.

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

Для чего-то вроде складки, если конечный результат - что-то вроде целого числа машины, вероятно, заканчивается в регистре процессора на весь период цикла. Таким образом, окончательный машинный код в значительной степени идентичен “ переменной с непрерывным доступом в C ”. (Если результатом является словарь или что-то еще, то, вероятно, нет. Но это то же самое, что и C.)

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

Все этого переговоры велик, и все, но если вы действительно хотите знать, как быстро конкретная программа Haskell является, теста он. Оказывается, хорошо написанные программы Haskell обычно бывают довольно быстрыми. (Как и большинство скомпилированных языков.)

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

+1

«Любой собранный мусором язык, вероятно, не ваш друг» Зависит. Противоположное может быть правдой. Объекты, расположенные ближе ко времени, заканчиваются в памяти. Очень красивая местность. – usr

+0

@usr Также зависит от размера кучи поколения 1, частоты GC, времени продвижения поколения и множества других материалов, которые варьируются от программы к программе, да. : -} – MathematicalOrchid

+0

GC стремится сохранить местность. Он удаляет мертвые дыры и сжимает живые объекты ближе друг к другу. См. Http://stackoverflow.com/questions/14023988/why-is-processing-a-sorted-array-slower-than-an-unsorted-array/14024191#14024191 для примера местоположения под GC. – usr

Смежные вопросы