Есть ли неатомарный эквивалент std :: shared_ptr? А почему его нет в <памяти>?

88

Это вопрос из двух частей, касающихся атомарности std::shared_ptr:

1. Насколько я могу судить, std::shared_ptrэто единственный умный указатель в <memory>этом атомарном. Мне интересно, есть ли std::shared_ptrдоступная неатомарная версия (я ничего не вижу <memory>, поэтому я также открыт для предложений, выходящих за рамки стандарта, например, в Boost). Я знаю, что boost::shared_ptrэто тоже атомарно (если BOOST_SP_DISABLE_THREADSне определено), но, может быть, есть другая альтернатива? Я ищу что-то с той же семантикой, что и std::shared_ptr, но без атомарности.

2. Я понимаю, почему std::shared_ptrатомарный; это довольно мило. Однако это подходит не для каждой ситуации, и в C ++ исторически существовала мантра «платите только за то, что вы используете». Если я не использую несколько потоков или если я использую несколько потоков, но не разделяю владение указателем между потоками, атомарный интеллектуальный указатель является излишним. Мой второй вопрос: почему std::shared_ptrв С ++ 11 не была представлена ​​неатомарная версия ? (при условии, что есть причина ) (если ответ просто «неатомарная версия просто никогда не рассматривалась» или «никто никогда не просил неатомарную версию», это нормально!).

Что shared_ptrкасается вопроса №2, мне интересно, предлагал ли кто-нибудь когда-нибудь неатомарную версию (либо для Boost, либо для комитета по стандартам) (не для замены атомарной версии shared_ptr, а для сосуществования с ней), и она была сбита для конкретная причина.

Кукурузные стебли
источник
4
Какая именно «стоимость» здесь вас беспокоит? Стоимость атомарного увеличения целого числа? Вас действительно беспокоит такая стоимость для любого реального приложения? Или вы просто преждевременно оптимизируете?
Никол Болас
9
@NicolBolas: Это больше любопытство, чем что-либо другое; У меня нет (в настоящее время) кода / проекта, где я серьезно хочу использовать неатомарный общий указатель. Тем не менее, у меня были проекты (в прошлом), в которых Boost shared_ptrзначительно замедлялся из-за его атомарности, и определение BOOST_DISABLE_THREADSимело заметную разницу (я не знаю, std::shared_ptrстоил бы такой же расход boost::shared_ptr).
Cornstalks
13
@ Близкие избиратели: какая часть вопроса неконструктивна? Если нет конкретной причины для второго вопроса, ничего страшного (простой ответ «это просто не рассматривался» был бы достаточным ответом). Мне любопытно , существует ли конкретная причина / обоснование. И я бы сказал, что первый вопрос, безусловно, уместен. Если мне нужно уточнить вопрос или внести в него небольшие изменения, сообщите мне. Но я не понимаю, насколько это неконструктивно.
Cornstalks
10
@Cornstalks Что ж, вероятно, люди не так хорошо реагируют на вопросы, которые они могут легко отклонить как «преждевременную оптимизацию» , независимо от того, насколько верен, хорошо сформулирован или актуален вопрос, я полагаю. Я для себя не вижу причин закрывать это как неконструктивное.
Кристиан Рау,
13
(не могу написать ответ, теперь он закрыт, поэтому комментирую) С GCC, когда ваша программа не использует несколько потоков shared_ptr, не использует атомарные операции для refcount. См. (2) на gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html патч для GCC, позволяющий использовать неатомарную реализацию даже в многопоточных приложениях для shared_ptrобъектов, которые не используются совместно потоки. Я сижу на этом патче много лет, но я подумываю о том, чтобы наконец сделать его для GCC 4.9
Джонатан Уэйкли

Ответы:

105

1. Мне интересно, доступна ли неатомарная версия std :: shared_ptr

Не предусмотрено стандартом. Он вполне может быть предоставлен «сторонней» библиотекой. Действительно, до C ++ 11 и до Boost казалось, что каждый написал свой собственный умный указатель с подсчетом ссылок (включая меня).

2. Мой второй вопрос: почему в C ++ 11 не была представлена ​​неатомарная версия std :: shared_ptr?

Этот вопрос обсуждался на встрече в Рапперсвиле в 2010 году. Этот вопрос был внесен в комментарий национального органа №20 Швейцарии. Обе стороны дискуссии приводили веские аргументы, в том числе те, которые вы привели в своем вопросе. Однако в конце обсуждения подавляющее большинство (но не единодушное) проголосовало против добавления несинхронизированной (неатомарной) версии shared_ptr.

Аргументы против включены:

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

  • Наличие одного «универсального» shared_ptr, который является «односторонним» трафиком при подсчете ссылок, имеет преимущества: Из исходного предложения :

    Имеет один и тот же тип объекта независимо от используемых функций, что значительно облегчает взаимодействие между библиотеками, включая сторонние библиотеки.

  • Стоимость атомики хоть и не нулевая, но не огромна. Стоимость снижается за счет использования конструкции перемещения и назначения перемещения, которые не требуют использования атомарных операций. Такие операции обычно используются при vector<shared_ptr<T>>стирании и вставке.

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

Последнее слово рабочей группы в Рапперсвиле в тот день было:

Отклонить CH 20. В настоящее время нет единого мнения о внесении изменений.

Говард Хиннант
источник
7
Вау, отлично, спасибо за информацию! Это именно та информация, которую я надеялся найти.
Cornstalks
> Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries. Это крайне странное рассуждение. Сторонние библиотеки в любом случае будут предоставлять свои собственные типы, так что какое это имеет значение, если они предоставили его в форме std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType> и т. Д.? вам всегда придется адаптировать свой код к тому, что возвращает библиотека,
Жан-Микаэль Селерье,
Это верно в отношении типов, специфичных для библиотеки, но идея состоит в том, что также есть много мест, где стандартные типы отображаются в сторонних API. Например, моя библиотека может std::shared_ptr<std::string>где-нибудь взять . Если чья-то библиотека тоже принимает этот тип, вызывающие абоненты могут передавать одни и те же строки нам обоим без неудобств или накладных расходов на преобразование между разными представлениями, и это небольшая победа для всех.
Джек О'Коннор
52

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

Хотя я полностью согласен с решением комитета, я действительно считаю, что использование несинхронизированного shared_ptrтипа в особых случаях дает определенные преимущества , поэтому я исследовал эту тему несколько раз.

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

В GCC, когда ваша программа не использует несколько потоков, shared_ptr не использует атомарные операции для refcount. Это выполняется путем обновления счетчиков ссылок с помощью функций-оберток, которые определяют, является ли программа многопоточной (в GNU / Linux это делается просто путем определения, связана ли программа с libpthread.so), и соответственно отправляют на атомарные или неатомарные операции.

Много лет назад я понял, что, поскольку GCC shared_ptr<T>реализован в терминах __shared_ptr<T, _LockPolicy>базового класса , можно использовать базовый класс с однопоточной политикой блокировки даже в многопоточном коде, явно используя __shared_ptr<T, __gnu_cxx::_S_single>. К сожалению, поскольку это не было предполагаемым вариантом использования, он не работал оптимально до GCC 4.9, и некоторые операции по-прежнему использовали функции оболочки и поэтому отправлялись на атомарные операции, даже если вы явно запросили _S_singleполитику. См. Пункт (2) на http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html.для получения дополнительных сведений и исправления для GCC, позволяющего использовать неатомарную реализацию даже в многопоточных приложениях. Я сидел на этом патче много лет, но, наконец, сделал его для GCC 4.9, который позволяет вам использовать подобный шаблон псевдонима для определения типа общего указателя, который не является потокобезопасным, но работает немного быстрее:

template<typename T>
  using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;

Этот тип не будет взаимодействовать std::shared_ptr<T>и будет безопасным в использовании только в том случае, если гарантируется, что shared_ptr_unsynchronizedобъекты никогда не будут совместно использоваться потоками без дополнительной синхронизации, предоставляемой пользователем.

Это, конечно, совершенно непереносимо, но иногда это нормально. При правильном взломе препроцессора ваш код по-прежнему будет нормально работать с другими реализациями, если shared_ptr_unsynchronized<T>это псевдоним shared_ptr<T>, с GCC он будет немного быстрее.


Если вы используете GCC до 4.9, вы могли бы использовать это, добавив _Sp_counted_base<_S_single>явные специализации в свой собственный код (и гарантируя, что никто никогда не будет создавать экземпляры __shared_ptr<T, _S_single>без включения специализаций, чтобы избежать нарушений ODR). Добавление таких специализаций stdтипов технически не определено, но будет работают на практике, потому что в этом случае нет никакой разницы между добавлением специализаций в GCC или их добавлением в ваш собственный код.

Джонатан Уэйкли
источник
2
Просто интересно, есть ли опечатка в вашем примере псевдонима шаблона? Т.е. я думаю, что следует читать shared_ptr_unsynchronized = std :: __ shared_ptr <. Между прочим, я тестировал это сегодня вместе с std :: __ enable_shared_from_this и std :: __ weak_ptr, и, похоже, он отлично работает (gcc 4.9 и gcc 5.2). Вскоре я профилирую / разбираю его, чтобы увидеть, действительно ли атомарные операции пропущены.
Карл Кук
Потрясающие детали! Недавно я столкнулся вопрос, как описано в этом вопросе , что в конечном итоге заставило меня заглянуть в исходный код std::shared_ptr, std::__shared_ptr, __default_lock_policyи тому подобное. Этот ответ подтвердил то, что я понял из кода.
Наваз
21

Мой второй вопрос: почему в C ++ 11 не была представлена ​​неатомарная версия std :: shared_ptr? (при условии, что есть причина).

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

Дизайн shared_ptr, переданный от Boost, заключался в создании минимального стандартного лингва-франка для интеллектуальных указателей. Что, вообще говоря, вы можете просто снять это со стены и использовать. Это то, что обычно используется в самых разных приложениях. Вы можете поместить его в интерфейс, и, скорее всего, хорошие люди захотят его использовать.

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

Было бы ужасно сбрасывать полдюжины интеллектуальных указателей с небольшими вариациями между ними в стандарт или, что еще хуже, в интеллектуальный указатель на основе политик. Каждый выберет указатель, который ему больше всего нравится, и откажется от всех остальных. Никто не сможет ни с кем общаться. Это будет похоже на текущие ситуации со строками C ++, где у каждого свой тип. Только намного хуже, потому что взаимодействие со строками намного проще, чем взаимодействие между классами интеллектуальных указателей.

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

std::vectorимеет некоторую неэффективность по сравнению с голыми массивами в некоторых случаях. У него есть некоторые ограничения; некоторые пользователи действительно хотят иметь жесткое ограничение на размер a vectorбез использования распределителя распределения. Однако комитет не планировал vectorбыть всем для всех. Он был разработан, чтобы использоваться по умолчанию для большинства приложений. Те, для кого это не работает, могут просто написать альтернативу, которая соответствует их потребностям.

Так же, как и для умного указателя shared_ptr, атомарность - это бремя. С другой стороны, можно также подумать о том, чтобы не копировать их так часто.

Николь Болас
источник
7
+1 за «можно было бы подумать не столько копировать их».
Али
Если вы когда-нибудь подключили профилировщик, вы особенный и можете просто отключить аргументы, подобные приведенным выше. Если у вас нет труднодостижимых рабочих требований, вам не следует использовать C ++. Рассуждая так, как вы, это хороший способ сделать C ++ универсальным для всех, кто интересуется высокой производительностью или малой задержкой. Вот почему игровые программисты не используют STL, boost или даже исключения.
Hans Malherbe
Для ясности, я думаю , что котировка в верхней части вашего ответа следует читать « почему не неатомическая версия станд :: shared_ptr представлена в C ++ 11?»
Шарль Савойя,
4

Готовлю доклад на shared_ptr на работе. Я использовал модифицированный boost shared_ptr, избегая отдельного malloc (например, что делает make_shared) и параметра шаблона для политики блокировки, такого как shared_ptr_unsynchronized, упомянутого выше. Пользуюсь программой от

http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html

в качестве теста, после очистки ненужных копий shared_ptr. Программа использует только основной поток, и отображается тестовый аргумент. Тестовый env - это ноутбук под управлением linuxmint 14. Вот время в секундах:

тестовый запуск настройки boost (1.49) std с измененным ускорением make_shared
mt-unsafe (11) 11,9 9 / 11,5 (-pthread включен) 8,4  
атомный (11) 13,6 12,4 13,0  
mt-unsafe (12) 113,5 85,8 / 108,9 (-pthread на) 81,5  
атомный (12) 126,0 109,1 123,6  

Только версия 'std' использует -std = cxx11, а -pthread, вероятно, переключает lock_policy в классе g ++ __shared_ptr.

По этим числам я вижу влияние атомарных инструкций на оптимизацию кода. В тестовом примере не используются контейнеры C ++, но vector<shared_ptr<some_small_POD>>он может пострадать, если объекту не нужна защита потока. Boost страдает менее вероятно, потому что дополнительный malloc ограничивает объем встраивания и оптимизации кода.

Мне еще предстоит найти машину с достаточным количеством ядер для стресс-тестирования масштабируемости атомарных инструкций, но использование std :: shared_ptr только при необходимости, вероятно, лучше.

русс
источник
3

Boost обеспечивает shared_ptrнеатомарный. Он называется local_shared_ptr, и его можно найти в библиотеке интеллектуальных указателей boost.

Квантовый физик
источник
+1 за короткий твердый ответ с хорошей цитатой, но этот тип указателя выглядит дорогостоящим - как с точки зрения памяти, так и времени выполнения из-за одного дополнительного уровня косвенности (local-> shared-> ptr vs shared-> ptr).
Red.Wave 02
@ Red.Wave Можете ли вы объяснить, что вы имеете в виду под косвенным обращением и как оно влияет на производительность? Вы имеете в виду, что это все shared_ptrравно со стойкой, хоть она и местная? Или вы имеете в виду, что с этим есть еще одна проблема? Документы говорят, что единственная разница в том, что это не атомарно.
The Quantum Physicist
Каждый локальный ptr ведет счет и ссылку на исходный общий ptr. Таким образом, любой доступ к конечному указателю требует перехода с локального на общий ptr, который затем является переходом на указателя. Таким образом, есть еще одно косвенное обращение, связанное с косвенным обращением из общего ptr. И это увеличивает накладные расходы.
Красная волна
@ Red.Wave Откуда вы берете эту информацию? Это: «Таким образом, любой доступ к конечному объекту требует перехода с локального на общий ptr» требует некоторой ссылки. Я не мог найти это в документации по ускорению. Опять же, я видел в документации, что там написано, что local_shared_ptrи shared_ptrони идентичны, за исключением атомарного. Мне искренне интересно узнать, правда ли то, что вы говорите, потому что я использую local_shared_ptrв приложениях, требующих высокой производительности.
The Quantum Physicist
3
@ Red.Wave Если вы посмотрите фактический исходный код github.com/boostorg/smart_ptr/blob/… вы увидите, что нет двойного косвенного обращения . Этот абзац в документации - всего лишь ментальная модель.
Илья Попов