Действительно ли алгоритмы без блокировок работают лучше, чем их аналоги с полными блокировками?

Раймонд Чен занимался огромный серия в lockfree алгоритмы. Помимо простых случаев функций InterlockedXxx, кажется, что преобладающий шаблон для всех них заключается в том, что они реализуют свои собственные блокировки. Конечно, блокировок процессора нет, но концепция циклического повторения на каждом процессоре для обеспечения согласованности очень похожа на спин-блокировку. И, будучи спин-блокировками, они будут менее эффективны, чем обычные блокировки, поставляемые с операционной системой, потому что они не передают контроль над своими квантами, ожидая других потоков. Поэтому всякий раз, когда кто-то приходит ко мне и говорит: «Но мой алгоритм безблокировочный», мой общий ответ — «так»?

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


person Billy ONeal    schedule 15.04.2011    source источник
comment
Я видел только несколько графиков по этой теме в Joe Duffy Concurrency. книга, однако не исчерпывающая. Также см. его блог bluebytesoftware для некоторых дополнительных статей.   -  person Chris O    schedule 15.04.2011
comment
Более гибкая, масштабируемая блокировка в JDK 5.0 имеет некоторые ориентиры.   -  person Sanjeevakumar Hiremath    schedule 15.04.2011
comment
Контрольные показатели: liblfds.org/wordpress/?p=89 (обратите внимание на шкалы бесплатных списков теперь лучше добавлена ​​экспоненциальная отсрочка; более поздние тесты показывают масштабирование 0,4 для двух потоков, и я еще не уверен, является ли период отсрочки оптимальным).   -  person    schedule 18.04.2011


Ответы (10)


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

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

При этом большая часть этого зависит от рассматриваемого алгоритма (и реализации). Например, у меня есть некоторые подпрограммы, которые мне удалось переключить на новые параллельные коллекции .NET 4 вместо использования прежних механизмов блокировки, и я оценил улучшение общей скорости алгоритма почти на 30%. При этом существует множество тестов, которые показывают снижение производительности при использовании некоторых из этих коллекций по сравнению с базовой блокировкой. Как и при любой оптимизации производительности, вы ничего не узнаете, пока не измерите.

person Reed Copsey    schedule 15.04.2011
comment
+1 - Конечно, это будет зависеть от конкретных алгоритмов, и нужно сравнить, чтобы увидеть - я просто пытаюсь бросить вызов восприятию того, что без блокировки во всех случаях приравнивается к лучшему. :) Я думаю, все согласятся с тем, что действительно хороший алгоритм, использующий блокировки, вероятно, превзойдет по производительности очень плохой алгоритм без блокировок, точно так же, как действительно хороший алгоритм без блокировок превзойдет по производительности очень плохой алгоритм, использующий блокировки. - person Billy ONeal; 15.04.2011
comment
@Billy: Да, но хороший алгоритм может быть лучше с блокировкой или без блокировки - это действительно зависит от того, что вам нужно заблокировать, как часто он блокируется и т. д. Как правило, без блокировки, как правило, лучше перед лицом высокий уровень параллелизма (чем больше параллелизма, тем больше он помогает)... - person Reed Copsey; 15.04.2011

Помимо простых случаев функций InterlockedXxx, кажется, что преобладающий шаблон для всех них заключается в том, что они реализуют свои собственные блокировки.

Ни один из приведенных здесь ответов, похоже, не раскрывает суть разницы между «без блокировки» цикл CAS и мьютекс или спин-блокировка.

Важным отличием является то, что неблокирующие алгоритмы гарантированно работают самостоятельно — без помощи других потоков. При блокировке или круговой блокировке любой плохой поток, который не может получить блокировку, полностью оказывается во власти потока, которому принадлежит блокировка. Плохой поток, который не может получить блокировку, не может ничего сделать, кроме ожидания (либо через ожидание занятости, либо через спящий режим с помощью ОС).

С алгоритмами без блокировок, которые зацикливаются на CAS, каждый поток гарантированно продвигается вперед независимо от того, что делают другие конкурирующие потоки. Каждая нить, по сути, контролирует свою судьбу. Да, возможно, ему все еще придется зацикливаться много раз, но количество циклов ограничено количеством конкурирующих потоков. По большей части он не может бесконечно зацикливаться. (На практике возможна активная блокировка, например, из-за LL/SC , который продолжает давать сбой из-за ложного совместного использования) - но опять же меры могут быть приняты самим потоком, чтобы справиться с этим - он не зависит от милости другого потока, удерживающего блокировку.

Что касается производительности, это зависит. Я видел вопиющие примеры того, как алгоритмы без блокировок полностью уступают своим аналогам с блокировками даже в условиях высокой конкуренции потоков. На машине x86-64 с Debian 7 я сравнил производительность между очередью C++ Boost.Lockfree (на основе алгоритма Майкла/Скотта) и обычным старым std::queue окружением std::mutex. В условиях высокой конкуренции потоков версия без блокировки была почти в два раза медленнее.

Так почему же? Что ж, производительность алгоритмов без блокировки в конечном итоге сводится к деталям реализации. Как алгоритм избегает ABA? Как это обеспечивает безопасное восстановление памяти? Существует так много вариантов... помеченные указатели, освобождение на основе эпохи, состояние RCU/неактивное состояние, указатели опасностей, общая сборка мусора в масштабе всего процесса и т. д. Все эти стратегии влияют на производительность, а некоторые также накладывают ограничения на то, как ваше приложение в целом можно спроектировать. В целом, по моему опыту, подходы с подсчетом ссылок (или подходы с тегированными указателями), как правило, работают плохо. Но альтернативы могут быть гораздо более сложными для реализации и требуют гораздо большей инфраструктуры высвобождения памяти, основанной на локальном хранилище потока или обобщенной сборке мусора.

person Charles Salvia    schedule 07.05.2016

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

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

person Jerry Coffin    schedule 15.04.2011
comment
Хм... Я не уверен, что верю этому аргументу. Вполне возможно, что с одним из этих алгоритмов возникнет взаимоблокировка — вы сами реализуете вещи, которые работают по принципу блокировки! Но даже если это устранит все ошибки взаимоблокировки и динамической блокировки, с этими ошибками легче справиться, отладить и устранить, чем с потенциальными ошибками согласованности данных, которые вы можете получить из-за ошибочных блокировок алгоритмов. -- Но +1 за последний абзац. - person Billy ONeal; 15.04.2011
comment
@Billy: посмотри внимательно: я не сказал, что это идет что-то гарантирует, только то, что может. С замками гораздо сложнее что-либо гарантировать, даже при самых благоприятных обстоятельствах. - person Jerry Coffin; 15.04.2011
comment
@Jerry: Хм.... Я бы сказал наоборот. Легче спорить о взаимоблокировке или живой блокировке, но гораздо сложнее спорить о согласованности данных (что, ИМХО, является более важным ограничением). - person Billy ONeal; 16.04.2011
comment
+1 Билли. Обоснование и доказательство достоверности алгоритмов без блокировок, будь то самые низкие (без препятствий) или самые высокие (без ожидания) гарантии, представляются гораздо более сложными, чем простая блокирующая структура. - person John Vint; 16.04.2011
comment
@Billy: О, не поймите меня неправильно: я не говорю, что легче добиться многого в целом или что-то в этом роде. Я просто говорю, что есть пара специфических вещей, которые легче доказать, когда вы освобождаетесь от блокировки. - person Jerry Coffin; 16.04.2011
comment
Официальное определение блокировки — это не просто использование атомарных операций вручную вместо мьютексов. На самом деле это означает, что прогресс всегда возможен для хотя бы несколько тем.. С мьютексом, если один поток, удерживающий блокировку, не может двигаться дальше. Отсутствие ожидания — более сильная гарантия: каждая операция требует не более конечного числа шагов. Я думаю, что это исключает циклы cmpxchg (я думаю, что @BillyONeal беспокоился о спиноподобных циклах). - person Peter Cordes; 29.10.2016

В Windows на x64 простой (без объединения массивов перед списком свободных мест) свободный список свободных блокировок примерно на порядок быстрее, чем список свободных мест на основе мьютекса.

На моем ноутбуке (Core i5) для одного потока, без блокировки, я получаю около 31 миллиона операций со свободным списком в секунду, а для мьютекса — около 2,3 миллиона операций в секунду.

Для двух потоков (на отдельных физических ядрах) с lock-free я получаю около 12,4 миллиона операций свободного списка на поток. С мьютексом я получаю около 80 ТЫСЯЧ операций в секунду.

person Community    schedule 18.04.2011
comment
Mutex здесь не подходит; мьютексы предназначены только для межпроцессного взаимодействия. Если вы делаете такие вещи, вам, вероятно, следует использовать критическую секцию... - person Billy ONeal; 18.04.2011
comment
Однако я отмечаю, что без блокировки все равно, находитесь ли вы в процессах; его производительность не изменится. Если CS быстрее, чем мьютекс, потому что он предназначен только для одного процесса, то у безблокировки есть возможность, которой нет у CS. - person ; 18.04.2011
comment
@Blank: и да, и нет. Да, алгоритм кросс-процесса не нужно модифицировать, но для этого потребуется сегмент разделяемой памяти, который не свободен с точки зрения доступа. (т. е. типичный цикл опроса без блокировки будет выполняться медленнее) - person Billy ONeal; 18.04.2011
comment
У меня сложилось впечатление, что доступ к общей памяти не медленнее, чем к неразделяемой памяти. Какие накладные расходы вы имеете в виду? код бенчмарка еще не опубликован — он является частью седьмого релиза liblfds, который выйдет через месяц или два. На данный момент доступна версия 6. liblfds.org - person ; 18.04.2011
comment
Тест очень прост; это всплывающее окно, а затем нажатие из/в свободный список в цикле while, где они (и счетчик) являются единственными операциями в цикле. Это работает в течение десяти секунд. Используются различные комбинации логических ядер. - person ; 18.04.2011
comment
@Blank: Сотрите это - похоже, вы правы. Большая часть накладных расходов на разделяемую память связана с локальностью кеша, что не будет иметь значения для такого теста, потому что все данные, вероятно, все время будут кэшироваться. Что касается того, почему было бы неплохо увидеть код, мы не можем объективно смотреть на тест, не имея возможности увидеть соответствующие биты кода, чтобы показать, что они эквивалентны (т. е. и что не выполняются излишне медленные вещи (путем т.е. опечатки) в любом алгоритме). - person Billy ONeal; 18.04.2011
comment
Вот результаты для критических секций; liblfds.org/wordpress/?p=203 - person ; 18.04.2011

Основное преимущество действительно свободных от блокировок алгоритмов заключается в том, что они надежны, даже если задача оказывается заблокированной (обратите внимание, что отсутствие блокировок — более жесткое условие, чем «неиспользование блокировок» (*)). Несмотря на то, что отсутствие ненужных блокировок дает преимущества в производительности, наиболее эффективными структурами данных часто являются те, которые могут работать с блокировками во многих случаях, но которые могут использовать блокировки для минимизации пробуксовки.

(*) Я видел несколько попыток «свободных от блокировки» очередей с несколькими производителями, когда производитель, застрявший в неподходящее время, не позволял потребителям видеть какие-либо новые элементы, пока он не завершит свою работу); такие структуры данных не следует называть «свободными от блокировки». Один производитель, который будет заблокирован, не помешает другим производителям добиться прогресса, но может произвольно заблокировать потребителей.

person supercat    schedule 08.05.2011

Алгоритмы без блокировки могут быть абсолютно быстрее, чем их блокирующий аналог. Но, конечно, верно и обратное. Предполагая, что реализация работает лучше, чем блокирующая часть, единственным ограничивающим фактором является конкуренция.

Возьмите два класса Java, ConcurrentLinkedQueue и LinkedBlockingQueue. В условиях умеренной конкуренции в реальном мире CLQ значительно превосходит LBQ. При интенсивной конкуренции использование приостанавливающих потоков позволит LBQ работать лучше.

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

person John Vint    schedule 15.04.2011

Недавно на [JavaOne Россия][1] сотрудник Oracle (который специализируется на производительности и тестах Java) показал некоторые измерения операций в секунду при параллельном доступе к простому счетчику int с использованием CAS (на самом деле безблокировочная, высокоуровневая спин-блокировка). и классические замки (java.util.concurrent.locks.ReentrantLock).

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

person leventov    schedule 15.04.2011
comment
Хм ... это очень специфично для языка Java и на самом деле ничего не говорит о блокировке или нет. И нет такой мысли, как спин-блокировка без блокировки — спин-блокировка — это блокировка. - person Billy ONeal; 18.04.2011
comment
Счетчик int не масштабируется независимо от того, что вы делаете, потому что у вас есть много потоков, обращающихся к одной и той же ячейке памяти. Вам нужна счетная воронка. - person ; 18.04.2011

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

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

person ccleve    schedule 15.04.2011
comment
+1 - Верно - но я думаю, мы можем предположить, что с обоими алгоритмами происходит разногласие (потому что, если бы это не было спорным моментом в вашей проблеме, маловероятно, что вы потратили бы время на его реализацию без блокировки). - person Billy ONeal; 15.04.2011
comment
Вопрос был о бенчмарках, а не о теории блокировок и безблокировочных алгоритмах. Мой частичный ответ предложил один из способов получения эталона. - person ccleve; 21.04.2011

Lock-free также имеет то преимущество, что он не спит. В ядре есть места, где вам не разрешено спать — в ядре Windows их куча — и это болезненно ограничивает вашу способность использовать структуры данных.

person Community    schedule 18.04.2011
comment
Спинлоки тоже не спят. - person Billy ONeal; 26.10.2011

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

person TakeMeAsAGuest    schedule 01.02.2019
comment
Я думаю, что вопрос имел в виду спросить о коде без блокировки, независимо от того, является ли он свободным от ожидания / без блокировки / без препятствий. (en.wikipedia.org/wiki/Non-blocking_algorithm). Как вы говорите, реализация этих свойств обычно того не стоит. Но безблокировочный код, который может блокировать (но на практике это почти никогда не происходит и ненадолго), может превзойти взлом блокировок. например очередь без блокировки может поддерживать одновременное чтение/запись. например Гарантии прогресса без блокировки — это хороший анализ хорошей очереди без блокировки, которая может блокироваться в редких крайних случаях. - person Peter Cordes; 02.02.2019
comment
ты чокнутый? он просит сравнить незаблокированный и заблокированный код. и в вашей ссылке это может быть один cmpx в неоспариваемом случае, но так же и со спинлоком. так что плохого в том, чтобы ждать, чтобы иметь возможность писать в синхронизированную память, вместо того, чтобы делать много работы и пытаться, если никто не делал этого раньше? опять же, я предполагаю, что вы не дурак, убивающий свои темы неожиданным образом или жадно вращающийся .. - person TakeMeAsAGuest; 02.02.2019