Дерево пальца (Data.Sequence) против веревки (Data.Rope) (Haskell или вообще) - PullRequest
16 голосов
/ 17 января 2012

Каковы основные различия между деревом пальца (Data.Sequence) и веревкой (Data.Rope) ( версия Эдварда Кметта или версия Пьера-Этьена Менье?

В библиотеках Haskell функция Data.Sequence имеет больше функций. Я думаю, что веревки обрабатывают «блоки» более эффективно.

Как программист, рассматривающий эффективность, работающую, скажем, с последовательностьюиз 7 миллионов символов, где мне нужно сделать (а) вставить в любом месте, (б) вырезать и вставить сегменты (сплайс), (в) поиск и замена подстрок, что более эффективно?

Пояснения в ответна ehird:

  1. Большая часть моего алгоритма выполняет тысячи операций поиска-замены , например s/(ome)?reg[3]x/blah$1/g, для многократного изменения данных.сопоставление с регулярным выражением (возможно, форма regex-tdfa ?) , а также объединение (data [a: b] = newData), где не обязательно (length(newData) == b-a+1)

  2. Lazy ByteStrings можетбыть в порядке, но как насчет сращивания ?Соединение ByteString - это O (dataSize / chunkSize) линейное время (для поиска) плюс (возможно?) Накладные расходы на поддержание фрагментов постоянного размера.(Может быть неправильно в последней части);vs O (log (dataSize)) для FingerTree.

  3. Мой тип данных "containee" является абстрактно конечным алфавитом .Это может быть представлено конкретно Char с или Byte с или Word8 с или даже что-то вроде гипотетического Word4 с (клев).** У меня есть связанный вопрос о том, как эффективно использовать newtype или data, чтобы мой код мог ссылаться на абстрактный алфавит, но скомпилированная программа все еще может быть эффективной.(Я должен опубликовать этот вопрос отдельно.)

  4. Проблемы производительности : Возможно, Seq намного хуже, чем ByteString (на q значительный постоянный коэффициент).В простых тестах чтение 7MB в строгом ByteString, а затем его печать на консоли достигает пика при 60 МБ реального использования памяти (согласно Windows Process Manager), но загрузка этого содержимого в Seq Char и затем печать использует 400 МБ!(Я должен опубликовать этот вопрос отдельно, с кодом и данными профилирования.)

  5. Проблемы с платформой : я использую EclipseFP и платформу Haskell,У меня установлен текст на моей машине, и я хотел попробовать, но моя среда Eclipse не может его найти.Я получаю серьезные неприятности, когда использую cabal install (устанавливаются несовместимые версии пакетов, --user против --global путаница), поэтому я хочу придерживаться пакетов Platform, которые EclipseFP может найти.Я думаю, что Text войдет в следующую версию Platform, так что это будет хорошо.

  6. Trifecta : я кратко видел Trifecta, и это только добавило моей путанице,(Почему у него есть свои новые реализации общих структур данных, которые уже были опубликованы? Они лучше? Слишком много почти идентичных опций!)

Отредактировано с более подробной информацией и улучшенными ссылками.

Этот вопрос получил большое значение.

@ Резюме ehird - главная точка отсчета.Веревка или Дерево Пальцев ByteStrings или Векторы плюс небольшой пользовательский моноид.В любом случае, мне придется написать простую реализацию регулярных выражений для склеивания.

Учитывая всю эту информацию, я бы порекомендовал либо Rope, либо создание вашей собственной структуры с пакетом fingertree, на котором он основан (а не Seq, чтобы вы могли правильно реализовать такие вещи, как длина, с помощью класса типа Measured (см. Monoids и Finger Trees), с листовыми данными, разделенными на части в распакованном векторе.Последнее, конечно, больше работы, но позволяет оптимизировать специально для вашего случая использования.В любом случае, определенно оберните это в абстрактный интерфейс.

Я вернусь позже сегодня и разделюсь на новые вопросы.Я разберусь с техническими вопросами низкого уровня, а затем вернусь к общему сравнению.яизменит название вопроса, чтобы лучше отражать мою реальную озабоченность "Какие модули Haskell предоставляют или поддерживают операции манипулирования последовательностями, которые мне нужны эффективно?"Спасибо Ёрд и другим респондентам.

1 Ответ

16 голосов
/ 17 января 2012

В остальной части этого ответа я буду предполагать, что вы на самом деле пытаетесь хранить необработанные байты , а не символы. Если вы хотите сохранить символы, то вам следует рассмотреть возможность использования text (эквивалент ByteString для текста Unicode) или написания собственной структуры на основе fingertree на его основе. Вы также можете использовать ByteString с модулем Data.ByteString.UTF8 из пакета utf8-string ; Я думаю, что это может оказаться более эффективным, но оно гораздо менее полнофункционально, чем Text для текста Unicode.

Что ж, пакет веревки, который вы связали, хранит только эквивалент ByteString s, тогда как Seq является общим и может обрабатывать данные любого типа; первый, вероятно, будет более эффективным для хранения строк байтов.

Я подозреваю, что это та же самая существенная древовидная структура, что и в веревке, реализующей "цепочки пальцев из байтов", а Seq - это дерево из 2-3 пальцев; это зависит от (и предположительно использует) пакета fingertree , который по сути такой же, как Data.Sequence, но более общий. Вполне вероятно, что веревка упаковывает данные в короткие ByteString с, что невозможно сделать с Seq (без прерывания таких операций, как length и т. Д.).

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

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

Вы уверены, что ByteString или Text (так как вы упомянули символы) не подходят для того, что вы делаете? Они хранят поля смещения и длины, так что взятие подстроки не вызывает никакого копирования. Если ваши операции вставки достаточно редки, то это может сработать. Стоит рассмотреть и некую структуру на основе IntMap.


В ответ на ваш обновленный вопрос:

  1. Регулярные выражения для пользовательских типов строк: Имейте в виду, что для использования существующей реализации регулярных выражений с "необычным" типом строки вам придется реализовать поддержку самостоятельно , чтобы склеить это к существующему коду regex-tdfa. Я не уверен, что результат будет в результате.
  2. Сращивание с ленивыми ByteString s: Обратите внимание, что ленивые ByteString s по умолчанию используют куски по 64 КиБ, и вы можете использовать куски любого размера, используя fromChunks вручную. Но вы правы, пальчиковое дерево, вероятно, лучше подходит; это просто больше работы, которая уже выполняется для вас с помощью lazy ByteString s.
  3. Конечный алфавит: ОК; Я бы посоветовал вам абстрагироваться (с newtype) от типа, представляющего последовательность этого алфавита. Таким образом, вы можете опробовать различные реализации, надеясь при этом локализовать работу, которую необходимо выполнить, так что вы можете выбирать, основываясь на реальных данных о производительности, а не на догадках :) Конечно, при написании новой реализации все еще есть первоначальные затраты. Что касается вашего дополнительного вопроса, newtype удаляются во время компиляции, поэтому newtype имеет такое же представление во время выполнения, что и тип, который он переносит. Короче говоря: не беспокойтесь об упаковке вещей в newtype s.
  4. Производительность Seq: Ну, это не удивительно.Seq Char полностью ленивый и в штучной упаковке, и не будет «ломаться» Char s вместе, как Rope;вероятно, это даже менее эффективно, чем String.Нечто подобное Seq ByteString может выполнить лот лучше, но если ваши куски не имеют постоянного размера, вы потеряете способность получить значимую длину и т. Д., Не проходя через все это.
  5. Проблемы с пакетом EclipseFP: Я бы не выбрал, какое представление использовать, исходя из таких простых проблем с инструментами, как этот;Я рекомендую задать новый вопрос.
  6. Trifecta: Я не думаю, что trifecta имеет отношение к вашей проблеме;он просто написан тем же автором, что и веревка, поэтому он имеет отношение к дальнейшему развитию веревки.Это просто библиотека комбинатора синтаксических анализаторов, такая как Parsec, и она больше фокусируется на диагностике и тому подобном, а не на производительности, поэтому я не думаю, что она может заменить ваши регулярные выражения.

Что касается # 3,вместо ByteString вы можете рассмотреть без коробки Vector;таким образом, вы можете использовать свой абстрактный тип алфавита, вместо того, чтобы взламывать вещи в ByteString * * * * * * * *. * * * *

интерфейсе.

*1093* Учитывая всю эту информацию, я бы порекомендовал либо Rope, либо создание собственногоструктура с пакетом fingertree , на котором он основан (а не Seq, так что вы можете правильно реализовать такие вещи, как length с классом типа Measured - см. Monoids иПальцевые деревья ), с листовыми данными, разбитыми на нераспакованные Vector.Последнее, конечно, больше работы, но позволяет оптимизировать специально для вашего случая использования.В любом случае, определенно оберните его в абстрактный интерфейс.

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

...