Пропустить Список против двоичного дерева

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

Ответ 1

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

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


Обновление от комментариев Jon Harrops

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

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

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

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

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

Ответ 2

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

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

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

В соответствии с некоторыми утверждениями выше вы можете иметь реализации двоичных поисковых деревьев (BST), которые хорошо работают при параллельном программировании. Потенциальная проблема с concurrency -фокусированными BSTs заключается в том, что вы не можете легко получить то же самое, что и гарантии относительно балансировки, как и из дерева красно-черных (RB). (Но "стандартные", т.е. случайные, списки пропуска не дают вам этих гарантий.) Там есть компромисс между поддержанием баланса во все времена и хорошим (и простым в программировании) одновременным доступом, поэтому обычно используются расслабленные деревья RB когда требуется хороший concurrency. Релаксация заключается в том, чтобы сразу не переустанавливать дерево. Для нескольких датированных (1998) обзоров см. Hanke "Выполнение параллельных алгоритмов красного-черного дерева" [ps.gz].

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

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

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

EDIT2: Я обнаружил (2015 опубликовал) материнство всех тестов: Gramoli "Больше, чем вы когда-либо хотели знать о синхронизации. Synchrobench, измерение влияния Синхронизация по параллельным алгоритмам" : Здесь приведено отрытое изображение, относящееся к этому вопросу.

enter image description here

"Algo.4" является предшественником (более ранняя версия 2011 года) Brown et al. упомянутое выше. (Я не знаю, насколько лучше или хуже версия 2014 года). "Algo.26" - это Herlihy, упомянутый выше; так как вы можете видеть, что он обрушивается на обновления и намного хуже на процессорах Intel, используемых здесь, чем на процессорах Sun от оригинальной бумаги. "Algo.28" - это ConcurrentSkipListMap из JDK; это не так, как можно было бы надеяться по сравнению с другими реализациями списков пропуска на основе CAS. Победителями, получившими высокую оценку, являются алгоритм Algo.2, основанный на блокировке (!!), описанный Crain et al. в "Сопротивляющее конфликты двоичное дерево поиска" и "Algo.30" - это "вращающийся скипист" из " Логарифмические структуры данных для multicores "." Algo.29 "- это " Нет горячего пятна, не блокирующего пропустить список ". Имейте в виду, что Gramoli является соавтором всех трех этих документов с алгоритмами поиска." Algo.27 "- это реализация списка пропусков Fraser на С++.

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

Из Wikipedia, которую вы цитировали:

Операции

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

Беспокойное дерево поиска бинарного поиска является большим и более часто используемым.

Пропустить Список Vs Splay Tree Vs Hash Table Runtime на поиск словаря op