Подтвердить что ты не робот

Стоимость прохода по shared_ptr

Я использую std:: tr1:: shared_ptr широко в моем приложении. Это включает в себя передачу объектов в качестве аргументов функции. Рассмотрим следующее:

class Dataset {...}

void f( shared_ptr< Dataset const > pds ) {...}
void g( shared_ptr< Dataset const > pds ) {...}
...

При передаче объекта набора данных через shared_ptr обеспечивается его существование внутри f и g, функции могут быть вызваны миллионы раз, что приводит к созданию и уничтожению большого количества объектов shared_ptr. Вот фрагмент плоского профиля gprof из недавнего прогона:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls   s/call   s/call  name
  9.74    295.39    35.12 2451177304     0.00     0.00  std::tr1::__shared_count::__shared_count(std::tr1::__shared_count const&)
  8.03    324.34    28.95 2451252116     0.00     0.00  std::tr1::__shared_count::~__shared_count()

Итак, ~ 17% времени выполнения было потрачено на подсчет ссылок с объектами shared_ptr. Это нормально?

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

void f( const Dataset& ds ) {...}

и заменяя вызовы

shared_ptr< Dataset > pds( new Dataset(...) );
f( pds );

с

f( *pds );

в тех местах, где я точно знаю, что объект не будет уничтожен, а поток программы внутри f(). Но прежде чем я убегу изменить группу сигнатур/звонков, мне хотелось узнать, что типичное поражение производительности при передаче shared_ptr было. Похоже, что shared_ptr не следует использовать для функций, вызываемых очень часто.

Любой ввод будет оценен. Спасибо за чтение.

-Artem

Обновление:. После изменения нескольких функций для принятия const Dataset& новый профиль выглядит следующим образом:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls   s/call   s/call  name
  0.15    241.62     0.37 24981902     0.00     0.00  std::tr1::__shared_count::~__shared_count()
  0.12    241.91     0.30 28342376     0.00     0.00  std::tr1::__shared_count::__shared_count(std::tr1::__shared_count const&)

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

4b9b3361

Ответ 1

Всегда передавайте shared_ptr с помощью const:

void f(const shared_ptr<Dataset const>& pds) {...} 
void g(const shared_ptr<Dataset const>& pds) {...} 

Изменить: Что касается проблем безопасности, упомянутых другими:

  • При использовании shared_ptr в значительной степени по всему приложению, передача по значению займет огромное количество времени (я видел, что он прошел 50 +%).
  • Используйте const T& вместо const shared_ptr<T const>&, когда аргумент не должен быть нулевым.
  • Использование const shared_ptr<T const>& безопаснее, чем const T*, когда производительность является проблемой.

Ответ 2

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

Ответ 3

Если вы не используете make_shared, не могли бы вы дать это? Располагая счетчик ссылок и объект в той же области памяти, вы можете увидеть прирост производительности, связанный с когерентностью кеша. В любом случае стоит попробовать.

Ответ 4

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

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

Предположительно вам понадобится shared_ptr (потому что если вы можете уйти с локальным объектом, вы бы не выделили один из кучи), но вы даже можете "кэшировать" результат разыменования shared_ptr:

void fn(shared_ptr< Dataset > pds)
{
   Dataset& ds = *pds;

   for (i = 0; i < 1000; ++i)
   {
      f(ds);
      g(ds);
   }
}

... потому что даже * pds требует больше памяти, чем это абсолютно необходимо.

Ответ 5

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

Единственными головами, которые я могу вам дать, является: предположим внутреннюю функцию f (t * ptr), если вы вызываете другую функцию, которая использует общие указатели, а вы делаете другую (ptr), а другая делает общий указатель необработанного указателя, Когда этот второй счетчик ссылок для общих указателей попадает в 0, вы фактически удалили свой объект... хотя вы этого не хотели. вы сказали, что вы часто ссылались на указатели ссылок, поэтому вам нужно следить за такими угловыми делами.

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