Пропустить список против бинарного дерева поиска - PullRequest
202 голосов
/ 02 ноября 2008

Недавно я наткнулся на структуру данных, известную как , пропустить список . Похоже, что он очень похож на бинарное дерево поиска.

Почему вы хотите использовать список пропусков поверх бинарного дерева поиска?

Ответы [ 7 ]

233 голосов
/ 04 ноября 2008

Пропуск списков более поддается одновременному доступу / изменению. Херб Саттер написал статью о структуре данных в параллельных средах. У него более глубокая информация.

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


Обновление от комментариев Джона Харропса

Я прочитал последнюю статью Фрейзера и Харриса Параллельное программирование без блокировок . Действительно хороший материал, если вы заинтересованы в структурах данных без блокировки. Статья посвящена Транзакционной памяти и теоретической операции MCAS с несколькими словами, сравнением и обменом. Оба они смоделированы в программном обеспечении, поскольку никакое оборудование еще не поддерживает их. Я весьма впечатлен тем, что им вообще удалось создать MCAS в программном обеспечении.

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

В разделе 8.2 они сравнивают производительность нескольких параллельных реализаций дерева. Я подведу итоги. Стоит скачать PDF-файл, так как он содержит несколько очень информативных графиков на страницах 50, 53 и 54.

  • Списки пропущенных блокировок безумно быстрые. Они невероятно хорошо масштабируются с количеством одновременных обращений. Это то, что делает списки пропусков особенными, другие структуры данных, основанные на блокировках, как правило, ломаются под давлением.
  • Списки пропусков без блокировки всегда быстрее, чем списки пропусков блокировки, но лишь незначительно.
  • списки пропущенных транзакций последовательно в 2-3 раза медленнее, чем версии с блокировкой и без блокировки.
  • блокировка красно-черных деревьев каркают при одновременном доступе. Их производительность линейно снижается с каждым новым пользователем. Из двух известных реализаций красно-чёрного дерева блокировок одна по существу имеет глобальную блокировку во время ребалансировки дерева. В другом используется причудливое (и сложное) повышение блокировки, но оно по-прежнему значительно не справляется с глобальной версией блокировки.
  • красно-черные деревья без блокировки не существует (больше не соответствует действительности, см. Обновление).
  • транзакционные красно-черные деревья сопоставимы с транзакционными пропущенными списками. Это было очень удивительно и очень многообещающе. Транзакционная память, хотя и медленнее, если ее легче писать. Это может быть так же просто, как быстрый поиск и замена в несовпадающей версии.

Обновление
Вот статья о деревьях без блокировки: Красно-черные деревья без блокировки с использованием CAS .
Я не углублялся в это глубоко, но на первый взгляд он кажется твердым.

72 голосов
/ 02 февраля 2015

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

Список пропусков эквивалентен случайно сбалансированному бинарному дереву поиска (RBST), что более подробно объясняется в статье Дина и Джонса «Изучение двойственности между списками пропусков и деревьями двоичного поиска» .

С другой стороны, вы можете также иметь детерминированные списки пропусков, которые гарантируют худшую производительность, ср. Манро и др.

Вопреки тому, что некоторые утверждают выше, у вас могут быть реализации двоичных деревьев поиска (BST), которые хорошо работают в параллельном программировании. Потенциальная проблема с BST, ориентированными на параллелизм, заключается в том, что вы не можете легко получить те же гарантии с балансировкой, что и из красно-черного (RB) дерева. (Но «стандартные», то есть рандомизированные, пропускающие списки также не дают вам этих гарантий.) Существует компромисс между поддержанием баланса в любое время и хорошим (и простым в программировании) параллельным доступом, поэтому relaxed Деревья RB обычно используются, когда требуется хороший параллелизм. Релаксация состоит в том, чтобы не перебалансировать дерево сразу. Несколько устаревший (1998 г.) обзор см. В статье Ханка «Производительность параллельных алгоритмов красно-черного дерева» [ps.gz] .

Одним из недавних улучшений является так называемое хроматическое дерево (в основном у вас есть некоторый вес, такой, что черный будет равен 1, а красный будет равен нулю, но вы также допускаете значения между ними) , И как цветное дерево обходится без списка пропусков? Давайте посмотрим, что Браун и соавт. «Общая техника для неблокирующих деревьев» (2014) должны сказать:

с 128 потоками наш алгоритм превосходит неблокирующий пропускаемый список Java от 13% до 156%, основанное на блокировке дерево AVL Bronson et al. от 63% до 224%, а RBT, использующий программную транзакционную память (STM), от 13 до 134 раз

РЕДАКТИРОВАТЬ, чтобы добавить: Список пропусков, основанный на блокировке Пью, который был оценен в Fraser and Harris (2007) «Параллельное программирование без блокировки» как близкий к их собственной версии без блокировки (точная точка зрения) настаивал на верхнем ответе здесь), также настроен для хорошей параллельной работы, ср. Pugh's "Одновременное ведение списков пропусков" , хотя и довольно мягко. Тем не менее, одна более новая статья 2009 года «Простой оптимистический алгоритм списка пропусков» , выполненная Herlihy et al., В которой предлагается предположительно более простая (чем у Пью) реализация одновременных списков пропусков на основе блокировок, критиковала Пью за отсутствие доказательство правильности достаточно убедительно для них. Оставляя в стороне этот (возможно, слишком педантичный) вопрос, Herlihy et al. показать, что их более простая реализация на основе блокировок списка пропусков фактически не масштабируется так же, как их реализация без блокировок в JDK, но только для высокой конкуренции (50% вставок, 50% удалений и 0% поисков) ... которые Fraser и Харрис не проверял вообще; Фрейзер и Харрис протестировали только 75% поисков, 12,5% вставок и 12,5% удалений (в пропущенном списке с элементами ~ 500K). Более простая реализация Herlihy et al. также приближается к решению без блокировок от JDK в случае низкой конкуренции, которую они протестировали (70% поисков, 20% вставок, 10% удалений); они фактически опередили решение без блокировок для этого сценария, когда сделали свой список пропусков достаточно большим, то есть с 200K до 2M элементов, так что вероятность конфликта при любой блокировке стала незначительной. Было бы неплохо, если бы Herlihy et al. пережил их зависание из-за доказательства Пью и проверил его реализацию, но, увы, они этого не сделали.

РЕДАКТИРОВАТЬ 2: Я нашел (опубликованный в 2015 году) исходный код для всех тестов: от Gramoli «Больше, чем вы когда-либо хотели знать о синхронизации. Synchrobench, Измерение влияния синхронизации на параллельные алгоритмы» : Вот отрывочное изображение, относящееся к этому вопросу.

enter image description here

«Algo.4» является предшественником (более старая, версия 2011 года) Брауна и др., Упомянутых выше. (Я не знаю, насколько лучше или хуже версия 2014 года). «Алго.26» - это упомянутое выше Херлихи; как вы можете видеть, он загружается при обновлениях, и гораздо хуже для процессоров Intel, используемых здесь, чем для процессоров Sun из оригинальной статьи. «Algo.28» - это ConcurrentSkipListMap из JDK; это не так хорошо, как можно было бы надеяться, по сравнению с другими реализациями списка пропуска на основе CAS. Победители в условиях высокой конкуренции - это алгоритм «Algo.2» (!!), описанный Crain et al. в «Дружественное для конкуренции дерево двоичного поиска» и «Algo.30» - это «вращающийся пропускающий список» из «Логарифмические структуры данных для многоядерный "." Algo.29 "- это " Отсутствует неблокируемая горячая точка list ". Имейте в виду, что Gramoli является соавтором всех трех из этих статей с алгоритмом победителя." Algo.27 "- реализация C ++ списка пропуска Фрейзера на C ++.

Вывод Gramoli заключается в том, что гораздо проще испортить реализацию параллельного дерева на основе CAS, чем аналогичный список пропусков. И на основании цифр трудно не согласиться. Его объяснение этому факту таково:

Сложность создания дерева без блокировки проистекает из сложность модификации нескольких ссылок атомарно. Пропустить списки состоят из башен, связанных друг с другом через указатели преемника и в котором каждый узел указывает на узел непосредственно под ним. Они есть часто считается похожим на деревья, потому что каждый узел имеет преемника однако в башне-преемнике и под ней основное различие заключается в что нисходящий указатель, как правило, является неизменным, следовательно, упрощается атомная модификация узла. Это различие, вероятно, причина, по которой пропущенные списки превосходят деревья в условиях сильной конкуренции как показано на рисунке [выше].

Преодоление этой трудности было ключевой проблемой в недавней работе Брауна и соавторов. У них есть отдельная (2013) статья "Прагматические примитивы для неблокирующих структур данных" на построении составных «примитивов» LL / SC, которые они называют LLX / SCX, которые сами реализуются с использованием (на уровне машины) CAS. Браун и соавт. использовал этот строительный блок LLX / SCX в их параллельной реализации дерева 2014 года (но не в 2011 году).

Я думаю, что, возможно, здесь также стоит обобщить основные идеи. из списка пропускаемых списков "без горячих точек" (CF). Он добавляет основную идею из расслабленных деревьев RB (и аналогичных структур данных): башни больше не создаются сразу после вставки, а откладываются до тех пор, пока не будет меньше конфликтов. И наоборот, удаление высокой башни может создать много споров; это наблюдалось еще в 1990 году, когда Пью публиковал список пропущенных списков, именно поэтому Пью ввел инверсию указателей при удалении (лакомый кусочек, который страница Википедии в списках пропуска до сих пор не упоминает, увы). Список пропусков CF делает этот шаг еще дальше и задерживает удаление верхних уровней высокой башни. Оба вида отложенных операций в списках пропуска CF выполняются отдельным (подобным CAS) отдельным потоком, подобным сборщику мусора, который его авторы называют «адаптирующимся потоком».

Код Synchrobench (включая все протестированные алгоритмы) доступен по адресу: https://github.com/gramoli/synchrobench. Последний Браун и соавт. реализация (не включенная в вышеприведенное) доступна по адресу http://www.cs.toronto.edu/~tabrown/chromatic/ConcurrentChromaticTreeMap.java У кого-нибудь есть машина с ядром 32+? J / K Моя точка зрения заключается в том, что вы можете запустить их сами.

12 голосов
/ 02 ноября 2008

Кроме того, в дополнение к полученным ответам (простота реализации в сочетании с сопоставимой производительностью сбалансированного дерева). Я считаю, что реализация обхода по порядку (вперед и назад) намного проще, потому что в списке пропусков фактически есть связанный список внутри реализации.

9 голосов
/ 16 ноября 2008

На практике я обнаружил, что производительность B-дерева в моих проектах оказалась лучше, чем в списках пропусков. Пропуск списков кажется проще для понимания, но реализация B-дерева - это не , а сложно.

Одно известное мне преимущество состоит в том, что некоторые умные люди разработали, как реализовать параллельный список пропусков без блокировки, который использует только атомарные операции. Например, Java 6 содержит класс ConcurrentSkipListMap, и вы можете прочитать исходный код, если вы сошли с ума.

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

8 голосов
/ 02 ноября 2008

Из статьи Википедии , которую вы цитировали:

Θ (n) операции, которые вынуждают нас посещать каждый узел в порядке возрастания (например, распечатывать весь список), дают возможность выполнить закулисную дерандомизацию структуры уровней списка пропусков в Оптимальный способ, приведя список пропусков к O (log n) времени поиска. [...] Пропустить список, по которому у нас нет недавно выполненные [любые такие] операции n (n), не обеспечить такой же абсолютный наихудший случай производительность гарантирует как больше традиционные сбалансированные данные дерева структуры , потому что это всегда возможно (хотя с очень низким вероятность) что использовались монеты построить список пропуска будет производить плохо сбалансированная структура

РЕДАКТИРОВАТЬ: так что это компромисс: Пропуск списки используют меньше памяти с риском, что они могут выродиться в несбалансированное дерево.

2 голосов
/ 24 марта 2009

Пропуск списков реализован с использованием списков.

Существуют решения без блокировок для одно- и двусвязных списков, но не существует решений без блокировок, которые напрямую используют только CAS для любой структуры данных O (logn).

Однако вы можете использовать списки на основе CAS для создания списков пропусков.

(Обратите внимание, что MCAS, созданный с использованием CAS, допускает произвольные структуры данных, и доказательство концепции красно-черного дерева было создано с использованием MCAS).

Итак, как ни странно, они оказываются очень полезными: -)

0 голосов
/ 06 апреля 2012

Пропуск списков имеет преимущество снятия блокировки. Но время прогона зависит от того, как определяется уровень нового узла. Обычно это делается с помощью Random (). В словаре из 56000 слов список пропусков занимал больше времени, чем простое дерево, а дерево занимало больше времени, чем хеш-таблица. Первые два не могут соответствовать времени выполнения хеш-таблицы. Кроме того, массив хеш-таблицы также может быть одновременно очищен от блокировки.

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

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

Пропуск списка против Splay Tree против Hash Table Runtime для словаря find op

...