Стоит ли передавать boost::shared_ptr<X> по константной ссылке?
С одной стороны, подсчёт ссылок может дорого стоить.
но недостатков много:
Добавляется косвенность (если ссылки реализованы как указатели).
Больше букв писать.
const ссылка может создавать иллюзию константности.
Оно хорошо только если типы совпадают, иначе — преобразование всё равно нужно.
class Y : X {};
void f(boost::shared_ptr<X const> const& p);
boost::shared_ptr<Y const> p1;
boost::shared_ptr<X> p2;
f(p1); // временый boost::shared_ptr<X const> создаётся
f(p2); // временый boost::shared_ptr<X const> создаётся
Как вообще бороться с оверхедом на подсчёт ссылок и стоит ли?
Кстати, в Delphi для типов, для которых подсчёт ссылок встроен в компилятор, всё просто и без увеличения уровня косвенности:
procedure P1(S: string; I: IUnknown);
begin(* неявное увеличение числа сссылок на S и AddRef для I *)
S := F1(); I := F2(); (* можно *)end; (* неявное уменьшение числа сссылок на S и Release для I *)procedure P2(const S: string; const I: IUnknown); (* передаются значения указателей, а не указатели на указатели *)begin(* неявных операций с I и S нет *)
S := F1(); I := F2(); (* нельзя - ошибка компиляции *)end; (* неявных операций с I и S нет *)
Здравствуйте, Alexander G, Вы писали:
AG>Стоит ли передавать boost::shared_ptr<X> по константной ссылке?
AG>С одной стороны, подсчёт ссылок может дорого стоить.
AG>но недостатков много:
AG> Добавляется косвенность (если ссылки реализованы как указатели).
Если приложение многопоточное (для однопоточного там вроде был какой-то макрос для shared_ptr, который убирал атомарные операции из shared_ptr), то стоит. Это покроет дополнительный уровень косвенности.
AG> Больше букв писать.
Ну с контейнерами же мы терпим На крайняк можно сделать что-то типа:
AG>Как вообще бороться с оверхедом на подсчёт ссылок и стоит ли?
*Может* стоить. Стоит ли в конкретном случае — скажет только профайлер.
Как бороться — тут есть 2 пути: консервативный и оперативный.
Консервативный — это убрать лишние операции над счётчиком ссылок (которых может быть великое множество). Во-первых, конечно, не передавать по значению. Во-вторых, не возвращать по значению. В-третьих, не делать лишних копий на стеке.
Вообще, изменение счётчика необходимо только в 2 случаях — кладём в динамический объект, берем из динамического объекта. Всё, что касается стека, вызова функций и т.д. тут необходимости менять счётчик нет.
В-четвёртых, не использовать подсчёт ссылок там, где управление временем жизни тривиально и без него. Например некоторые любят использовать shared_ptr в таких вещах, как очереди производитель-потребитель и многопоточные хэш-мапы; тут тривиально реализовать без него — производитель создал объект, положил в очередь, потребитель взял объект из очереди, обработал, удалил (пока объект живёт у производителя и потребителя его можно защитить auto_ptr).
Если консервативное лечение не помогает, то оперативный способ — использовать другую схему управления временем жизни. Вообще говоря, атомарный подсчёт ссылок — анти-паттерн масштабируемости.
AG>Кстати, в Delphi для типов, для которых подсчёт ссылок встроен в компилятор, всё просто и без увеличения уровня косвенности:
В С++ это тоже тривиально — используй интрузивный умный указатель (boost::intrusive_ptr). Если производительность важна, то неинтрузивный shared_ptr имеет смысл использовать только для прототипирования; а для реального кода — интрузивный (такая же история и с контейнерами — boost::intrusive).
При использовании интрузивного подсчёта ссылок в функцию можно передавать обычный указатель на объект, а умный указатель всегда можно из него восстановить при необходимости.
AG>
AG>procedure P1(S: string; I: IUnknown);
AG>begin(* неявное увеличение числа сссылок на S и AddRef для I *)
AG> S := F1(); I := F2(); (* можно *)
AG>end; (* неявное уменьшение числа сссылок на S и Release для I *)
AG>
Зачем при обычном вызове функции надо менять счётчик 2 раза? Разве вызывающий код может освободить счётчик до возврата из функции?
AG>Стоит ли передавать boost::shared_ptr<X> по константной ссылке?
У меня были ситуации, когда по результатам профилирования оказывалось оправданным передавать shared_ptr по константной ссылке.
AG>но недостатков много:
AG> Добавляется косвенность (если ссылки реализованы как указатели).
При передаче по значению еще и пляски на тему исключений будут. А что могут быть за затраты на косвенность при обращении с объектом? С ним же один черт работа как через ссылку.
AG> Больше букв писать. AG> const ссылка может создавать иллюзию константности. AG> Оно хорошо только если типы совпадают, иначе — преобразование всё равно нужно. AG>
AG>Как вообще бороться с оверхедом на подсчёт ссылок и стоит ли?
Если семантика позволяет — лучше вообще передавать X const&. IMHO.
AG>Кстати, в Delphi для типов, для которых подсчёт ссылок встроен в компилятор, всё просто и без увеличения уровня косвенности:
А вот лишний уровень косвенности я что-то не припоминаю, чтобы компилятор (VC) хоть раз сгородил. Явного указателя на указатель в коде же нет
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Тот кто сидит в пруду, Вы писали:
AG>>Стоит ли передавать boost::shared_ptr<X> по константной ссылке?
ТКС>У меня были ситуации, когда по результатам профилирования оказывалось оправданным передавать shared_ptr по константной ссылке.
А какие-нибудь цифры помнишь? Сколько он ел до оптимизации, сколько — после? Просто ради интереса.
AG>>но недостатков много:
AG>> Добавляется косвенность (если ссылки реализованы как указатели).
ТКС>При передаче по значению еще и пляски на тему исключений будут. А что могут быть за затраты на косвенность при обращении с объектом? С ним же один черт работа как через ссылку.
Если умный указатель передаётся по-значению, то это равнозначно передаче указателя на объект.
Если же передаётся ссылка на умный указатель, то это — указатель на указатель на объект.
Согласен, что если не требуется иного, то в функцию лучше передавать просто указатель/ссылку на сам объект, а не умный указатель.
Здравствуйте, remark, Вы писали:
AG>>Как вообще бороться с оверхедом на подсчёт ссылок и стоит ли?
R>*Может* стоить. Стоит ли в конкретном случае — скажет только профайлер.
Профайлер скажет. Вопрос в том, что писать до тех пор, пока профайлер не потребовался, чтобы не пессимизировать преждевременно.
AG>>Кстати, в Delphi для типов, для которых подсчёт ссылок встроен в компилятор, всё просто и без увеличения уровня косвенности:
R>В С++ это тоже тривиально — используй интрузивный умный указатель (boost::intrusive_ptr). Если производительность важна, то неинтрузивный shared_ptr имеет смысл использовать только для прототипирования; а для реального кода — интрузивный (такая же история и с контейнерами — boost::intrusive). R>При использовании интрузивного подсчёта ссылок в функцию можно передавать обычный указатель на объект, а умный указатель всегда можно из него восстановить при необходимости.
Кстати, похожее возможно с boost::shared_ptr при использовании boost::enable_shared_from_this.
AG>>
AG>>procedure P1(S: string; I: IUnknown);
AG>>begin(* неявное увеличение числа сссылок на S и AddRef для I *)
AG>> S := F1(); I := F2(); (* можно *)
AG>>end; (* неявное уменьшение числа сссылок на S и Release для I *)
AG>>
R>Зачем при обычном вызове функции надо менять счётчик 2 раза? Разве вызывающий код может освободить счётчик до возврата из функции?
т.к. переданы I и S "по значению", в самой вызывающей функции этим переменным могут присваиваться другие значения, при присвоении, как обычно счётчик ссылок правой части увеличивается, затем левой уменьшается. Для балланса необходимо это неявное увеличение/уменьшение счётчика ссылок на входе/выходе.
при передаче "по константному значению" возможность присвоение переменной нового значения отсутствует.
наверное, можно было бы в boost::shared_ptr предусмотреть на этот случай класс, который вёл бы себя как shared_ptr без =, reset и swap, который бы не дёргал счётчик ссылок. но у boost::shared_ptr приоритеты — корректность и единообразие, а не оптимальность и настраиваемость.
Здравствуйте, remark, Вы писали:
AG>>>Стоит ли передавать boost::shared_ptr<X> по константной ссылке?
ТКС>>У меня были ситуации, когда по результатам профилирования оказывалось оправданным передавать shared_ptr по константной ссылке.
R>А какие-нибудь цифры помнишь? Сколько он ел до оптимизации, сколько — после? Просто ради интереса.
Пока было копирование — в выводе профайлера оно присутствовало на уровне единиц процентов. После замены на ссылки оно, естественно, ушло совсем. Ну и число собранных сэмплов интересующего фрагмента кода тоже на несколько процентов ушло вниз, что подтверждает влияние именно копирования/разрушения shared_ptr. Но это ж не бенчмарка, которая для подтверждения какой-то гипотезы писалась, просто в конкретном куске кода так совпало, да и профилировалось оно по другому поводу.
AG>>>но недостатков много:
AG>>> Добавляется косвенность (если ссылки реализованы как указатели).
ТКС>>При передаче по значению еще и пляски на тему исключений будут. А что могут быть за затраты на косвенность при обращении с объектом? С ним же один черт работа как через ссылку.
R>Если умный указатель передаётся по-значению, то это равнозначно передаче указателя на объект.
Нет. Это равнозначно созданию нового объекта типа "умный указатель", установке/разрушению фреймов исключений, вызове методов shared_ptr(), разрушению объекта.
R>Если же передаётся ссылка на умный указатель, то это — указатель на указатель на объект.
Не-не-не. Вызов методов объекта типа shared_ptr<X> будет осуществляться ровно так же, как и в предыдущем случае. Хотя он скорее всего и проинлайнится, картины это изменить не должно.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Alexander G, Вы писали:
ТКС>>При передаче по значению еще и пляски на тему исключений будут.
AG>
AG>Конструкторы сматрпоинтеров не бросают исключений
Так а порушить-то его в случае чего все-равно надо.
Вот иллюстрация:
__declspec(noinline) int foo(boost::shared_ptr<X> x)
{
return x->test();
}
Здравствуйте, Alexander G, Вы писали:
AG>>>Как вообще бороться с оверхедом на подсчёт ссылок и стоит ли?
R>>*Может* стоить. Стоит ли в конкретном случае — скажет только профайлер.
AG>Профайлер скажет. Вопрос в том, что писать до тех пор, пока профайлер не потребовался, чтобы не пессимизировать преждевременно.
Если так, то стоит, дабы не пессимизировать преждевременно.
AG>>>Кстати, в Delphi для типов, для которых подсчёт ссылок встроен в компилятор, всё просто и без увеличения уровня косвенности:
R>>Зачем при обычном вызове функции надо менять счётчик 2 раза? Разве вызывающий код может освободить счётчик до возврата из функции?
AG>т.к. переданы I и S "по значению", в самой вызывающей функции этим переменным могут присваиваться другие значения, при присвоении, как обычно счётчик ссылок правой части увеличивается, затем левой уменьшается. Для балланса необходимо это неявное увеличение/уменьшение счётчика ссылок на входе/выходе. AG>при передаче "по константному значению" возможность присвоение переменной нового значения отсутствует.
Сделали поддержку в компляторе, и так облажались
AG>наверное, можно было бы в boost::shared_ptr предусмотреть на этот случай класс, который вёл бы себя как shared_ptr без =, reset и swap, который бы не дёргал счётчик ссылок. но у boost::shared_ptr приоритеты — корректность и единообразие, а не оптимальность и настраиваемость.
По-хорошему нужен указатель, который поддерживает одновременные обращения из разных потоков (то, что сейчас вынесено в отдельные atomic_* функции); указатель, который не поддерживает обращения из разных потоков, но поддерживает разделение объекта между потоками (текущий shared_ptr); однопоточный указатель (нет в boost), и умный указатель для передачи как параметр в функции и возвращения из функций (по сути — просто обёртка над указателем).
Тогда можно было бы точно подбирать требуемый вариант, не компромитируя единообразие синтаксиса.
Здравствуйте, Тот кто сидит в пруду, Вы писали:
ТКС>>>У меня были ситуации, когда по результатам профилирования оказывалось оправданным передавать shared_ptr по константной ссылке.
R>>А какие-нибудь цифры помнишь? Сколько он ел до оптимизации, сколько — после? Просто ради интереса.
ТКС>Пока было копирование — в выводе профайлера оно присутствовало на уровне единиц процентов. После замены на ссылки оно, естественно, ушло совсем. Ну и число собранных сэмплов интересующего фрагмента кода тоже на несколько процентов ушло вниз, что подтверждает влияние именно копирования/разрушения shared_ptr. Но это ж не бенчмарка, которая для подтверждения какой-то гипотезы писалась, просто в конкретном куске кода так совпало, да и профилировалось оно по другому поводу.
Спасибо, понятно, т.е. не особо большие потери были на лишние операции со счётчиком.
Здравствуйте, Тот кто сидит в пруду, Вы писали:
AG>>>> Добавляется косвенность (если ссылки реализованы как указатели).
ТКС>>>При передаче по значению еще и пляски на тему исключений будут. А что могут быть за затраты на косвенность при обращении с объектом? С ним же один черт работа как через ссылку.
R>>Если умный указатель передаётся по-значению, то это равнозначно передаче указателя на объект.
ТКС>Нет. Это равнозначно созданию нового объекта типа "умный указатель", установке/разрушению фреймов исключений, вызове методов shared_ptr(), разрушению объекта.
Это понятно, но не имеет отношения к косвенности.
R>>Если же передаётся ссылка на умный указатель, то это — указатель на указатель на объект.
ТКС>Не-не-не. Вызов методов объекта типа shared_ptr<X> будет осуществляться ровно так же, как и в предыдущем случае. Хотя он скорее всего и проинлайнится, картины это изменить не должно.
Это каким-таким образом передача по значению и по ссылке будет одинаковой???
Вот тест, скомпилированной студией в релиз режиме. При передаче по ссылке — лишний уровень косвенности:
Здравствуйте, remark, Вы писали:
AG>> Добавляется косвенность (если ссылки реализованы как указатели).
R>Если приложение многопоточное (для однопоточного там вроде был какой-то макрос для shared_ptr, который убирал атомарные операции из shared_ptr), то стоит. Это покроет дополнительный уровень косвенности.
Тут ещё есть тонкий момент. Производительность будет зависеть от того, разделяется ли данный умный указатель между потоками или нет (ещё/уже/вообще). Если не разделяется, то стоимость операций со счётчиком относительно велика, но не катастрофична. Если же он разделяется, то это можнет положить систему.
Предельно плохой сценарий будет такой. В программе есть некий глобальный объект (типа настроек, или таблицы какой-то), множество рабочих потоков в начале обработки очередного запроса "захватювают" этот объект и дальше начинают передавать в разные функции как shared_ptr по значению. В такой ситуации все эти операции со счётчиком (не считая первой и последней) абсолютно бессмысленны, но могут полностью уложить систему, т.к. множество потоков при каждой операции будут конкурировать друг с другом.
Здравствуйте, remark, Вы писали:
R>>>Если же передаётся ссылка на умный указатель, то это — указатель на указатель на объект.
ТКС>>Не-не-не. Вызов методов объекта типа shared_ptr<X> будет осуществляться ровно так же, как и в предыдущем случае. Хотя он скорее всего и проинлайнится, картины это изменить не должно.
R>Это каким-таким образом передача по значению и по ссылке будет одинаковой???
Передача — по разному, вызов — как сложится, оптимизатор всякое вытворить может. Обычно, IMHO, вызов одинаково выполняется.
R>Вот тест, скомпилированной студией в релиз режиме. При передаче по ссылке — лишний уровень косвенности:
R>
Зато в первом случае компилятор догадался поменять call на jmp
Вообще как фишка ляжет. Я рядом кусочек кода постил — там компилятор в случае с константной ссылкой догадался fastcall применить, обошлось без лишней косвенности. Щас попробую усложнить, интересно че нагенерит.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
ТКС>Вообще как фишка ляжет. Я рядом кусочек кода постил — там компилятор в случае с константной ссылкой догадался fastcall применить, обошлось без лишней косвенности. Щас попробую усложнить, интересно че нагенерит.
Оффтопик: оптимизатор вообще отжигает не по детски:
__declspec(noinline) int __stdcall bar(char const *name, int q, boost::shared_ptr<X> const & x)
{
std::cout << name << q;
if (x->test2() > 0.5)
return x->test();
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
boost::shared_ptr<X> a(new X);
foo("value", 10, a);
bar("reference", 10, a);
return 0;
}
Здравствуйте, Тот кто сидит в пруду, Вы писали:
ТКС>Здравствуйте, remark, Вы писали:
R>>>>Если же передаётся ссылка на умный указатель, то это — указатель на указатель на объект.
ТКС>>>Не-не-не. Вызов методов объекта типа shared_ptr<X> будет осуществляться ровно так же, как и в предыдущем случае. Хотя он скорее всего и проинлайнится, картины это изменить не должно.
R>>Это каким-таким образом передача по значению и по ссылке будет одинаковой???
ТКС>Передача — по разному, вызов — как сложится, оптимизатор всякое вытворить может. Обычно, IMHO, вызов одинаково выполняется.
Как бы оно не сложилось, перед вызовом будет 2 разыменования.
R>>Вот тест, скомпилированной студией в релиз режиме. При передаче по ссылке — лишний уровень косвенности:
R>>
ТКС>Зато в первом случае компилятор догадался поменять call на jmp ТКС>Вообще как фишка ляжет. Я рядом кусочек кода постил — там компилятор в случае с константной ссылкой догадался fastcall применить, обошлось без лишней косвенности. Щас попробую усложнить, интересно че нагенерит.
Здравствуйте, Тот кто сидит в пруду, Вы писали:
ТКС>>Вообще как фишка ляжет. Я рядом кусочек кода постил — там компилятор в случае с константной ссылкой догадался fastcall применить, обошлось без лишней косвенности. Щас попробую усложнить, интересно че нагенерит.
ТКС>Оффтопик: оптимизатор вообще отжигает не по детски:
ТКС>Что называется — не мытьем, так катаньем. Встраивать функцию ему запретили, так он константные параметры внутрь затащил
Если отключить LTCG, то всё встанет на свои места.
LTCG штука конечно хорошая, но для больших проектов она не всегда работает. Т.е. результат, полученный для тестового проекта, не всегда будет иметь место для большого проекта.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, Тот кто сидит в пруду, Вы писали:
ТКС>>>Вообще как фишка ляжет. Я рядом кусочек кода постил — там компилятор в случае с константной ссылкой догадался fastcall применить, обошлось без лишней косвенности. Щас попробую усложнить, интересно че нагенерит.
ТКС>>Оффтопик: оптимизатор вообще отжигает не по детски:
ТКС>>Что называется — не мытьем, так катаньем. Встраивать функцию ему запретили, так он константные параметры внутрь затащил
R>Если отключить LTCG, то всё встанет на свои места. R>LTCG штука конечно хорошая, но для больших проектов она не всегда работает. Т.е. результат, полученный для тестового проекта, не всегда будет иметь место для большого проекта.
Да, действительно, без LTCG убрать лишнюю косвенность оптимизатору не удается. Хотя все равно — для того, чтобы передача shared_ptr по ссылке отстала от передачи по значению, функция должна быть очень уж своеобразная. Потому что на то, чтобы сложить при необходимости результат первого разыменования в регистр у оптимизатора "ума" хватает, а фора (конструктор+деструктор+(опционально — расходы на поддержку исключений)) у передачи по ссылке большая.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, remark, Вы писали:
R>>>Зачем при обычном вызове функции надо менять счётчик 2 раза? Разве вызывающий код может освободить счётчик до возврата из функции?
AG>>т.к. переданы I и S "по значению", в самой вызывающей функции этим переменным могут присваиваться другие значения, при присвоении, как обычно счётчик ссылок правой части увеличивается, затем левой уменьшается. Для балланса необходимо это неявное увеличение/уменьшение счётчика ссылок на входе/выходе. AG>>при передаче "по константному значению" возможность присвоение переменной нового значения отсутствует.
R>Сделали поддержку в компляторе, и так облажались
Я так и не понял, к чему претензии.
Если передаём по значению, значит может потребоваться присвоить переменной новое значение. Как внутри функции, так и передав куда-то как var параметр (параметр по ссылке). т.е. на C++ было бы как-то так
void f(boost::shared_array<char> s); // здесь увеличение числа ссылок
{
s = f1(); // может поменяться
}// здесь уменьшение числа ссылок
Конечно, можно было бы обнаруживать, когда переменная не меняется и устранять лишний подсчёт ссылок для этого случая. Возможно, более новая версия Delphi так и делает.