Накладные расходы и реализация использования shared_ptr

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

Первый вопрос:

Я хочу знать, есть ли у реализации -> operator в shared_ptr дополнительные накладные расходы (например, больше, чем unique_ptr) во время выполнения. Объекты, о которых я говорю, обычно представляют собой долгоживущие экземпляры, копируемые только один раз после создания (когда я распределяю их между потоками), тогда я обращаюсь только к методам и полям этих объектов.

Я знаю, что shared_ptr защищают только подсчет ссылок.

Второй вопрос:

Насколько хорошо shared_ptr оптимизированы в libstdc ++? Всегда ли он использует мьютекс или использует преимущества атомарных операций (я сосредотачиваюсь на платформах x86 и ARM)?


person Goofy    schedule 05.06.2012    source источник
comment
В хорошей реализации shared_ptr при разыменовании указателя через -> не должно быть никаких накладных расходов. Я не знаком с libstdc ++, поэтому не могу ответить на ваш второй вопрос. Однако у вас есть заголовки, поэтому вы можете легко узнать, взглянув на то, как это реализовано.   -  person James McNellis    schedule 05.06.2012
comment
Если код является многопоточным, общий указатель GCC использует std::atomic<int> или что-то подобное для счетчика ссылок; Является ли это настоящим аппаратным (безблокировочным) атомарным, зависит от версии компилятора - я считаю, что это было улучшено в GCC 4.7.0.   -  person Kerrek SB    schedule 05.06.2012
comment
Копирование / присвоение / выход за пределы области действия имеет дополнительные накладные расходы из-за потоковобезопасного приращения счетчика ссылок. operator-> выглядит точно так же, как и старый добрый auto_ptr, т.е. можно ожидать нулевых накладных расходов.   -  person Damon    schedule 05.06.2012
comment
Этот вопрос в его нынешней форме слишком широк, чтобы на него можно было ответить. Существует множество реализаций shared_ptr и много версий GCC и libstdc ++. О каком вы говорите?   -  person Nicol Bolas    schedule 05.06.2012


Ответы (2)


Первый вопрос: использование operator->

Все реализации, которые я видел, имеют локальный кеш T* прямо в классе shared_ptr<T>, так что поле находится в стеке, поэтому operator-> имеет сопоставимые затраты с использованием локального стека T*: никаких накладных расходов.

Второй вопрос: мьютекс / атомикс

Я ожидаю, что libstdc ++ будет использовать атомики на платформе x86, будь то стандартные средства или специальные встроенные функции g ++ (в более старых версиях). Я считаю, что реализация Boost уже сделала это.

Однако я не могу комментировать ARM.

Примечание. C ++ 11 вводит семантику перемещения, поэтому при использовании shared_ptr многих копий естественно избегать.

Примечание: прочтите о правильном использовании shared_ptr здесь, вы можете использовать ссылки на shared_ptr (const или нет), чтобы избежать большинства копий / уничтожений в целом, поэтому их производительность не слишком важна.

person Matthieu M.    schedule 05.06.2012
comment
в ответе, который вы прикрепили, говорится, что используется make_shared. Интересно, как использовать этот шаблон в списке инициализации конструктора? Например, у класса Foo есть поле shared_ptr<int> num, поэтому конструктор должен выглядеть так: Foo::Foo(void) : num (move (make_shared (new int (30)))) { ... }? - person Goofy; 06.06.2012
comment
@Goofy: нет, с make_shared вы не выполняете new явно, а с другой стороны, вам нужно явно передать тип созданного объекта; также вызов move временно не нужен. Следовательно, он дает: Foo::Foo(): num(std::make_shared<int>(30)) {} - person Matthieu M.; 06.06.2012
comment
Хорошо, отлично :) Я все еще привыкаю к ​​rvalue в C ++;) - person Goofy; 06.06.2012

Shared_ptr GCC не будет использовать блокировку или атомику в однопоточном коде. В многопоточном коде он будет использовать атомарные операции, если ЦП поддерживает атомарные инструкции сравнения и замены, в противном случае счетчики ссылок защищены мьютексом. На i486 и более поздних версиях он использует атомику, i386 не поддерживает cmpxchg, поэтому использует реализацию на основе мьютексов. Я считаю, что ARM использует атомику для архитектуры ARMv7 и более поздних версий.

(То же самое относится как к std::shared_ptr, так и к std::tr1::shared_ptr.)

person Jonathan Wakely    schedule 06.06.2012
comment
Как GCC узнает, будет ли код многопоточным или нет? - person Drew Noakes; 17.11.2013
comment
@DrewNoakes - вы должны указать это с помощью #define. - person Martin James; 17.11.2013
comment
У вас есть ссылка на это? Я немного поискал и не нашел. - person Drew Noakes; 17.11.2013
comment
@Jonathan Wakely - один из разработчиков libstdc ++. Хотя мне также было бы неплохо, если бы вы могли уточнить это немного подробнее, например, что такое определение, что по умолчанию? - person Stephan Dollberg; 17.11.2013
comment
В этом вопросе упоминается BOOST_SP_DISABLE_THREADS, но мне интересно, есть ли версия для std::shared_ptr. - person Drew Noakes; 17.11.2013
comment
Нет #define. Libstdc ++ использует функции, которые отправляются (во время выполнения) в атомарную реализацию или неатомарную реализацию в зависимости от того, связана ли программа с libpthread или нет. Если он связан с libpthread, то предполагается, что существует несколько потоков и используется атомарный impl. Если программа не связана с libpthread, тогда программа является однопоточной, и поэтому используется неатомарный impl. - person Jonathan Wakely; 18.11.2013
comment
Краткую документацию можно найти на странице gcc.gnu. org / onlinedocs / libstdc ++ / manual / в разделе выбора политики блокировки. - person Jonathan Wakely; 18.11.2013