std :: unique_ptr в два раза больше базового объекта - PullRequest
9 голосов
/ 28 ноября 2011

У меня проблема с (в частности, с реализацией MSFT VS 10.0) std :: unique_ptrs.Когда я создаю std :: list из них, я использую вдвое больше памяти, чем когда я создаю std :: list только базового объекта (примечание: это большой объект - ~ 200 байт, так что это не простодополнительный счетчик ссылок валяется).

Другими словами, если я запускаю:

std::list<MyObj> X;
X.resize( 1000, MyObj());

моему приложению потребуется вдвое меньше памяти, чем при запуске:

std::list<std::unique_ptr<MyObj>> X;
for ( int i=0; i<1000; i++ ) X.push_back(std::unique_ptr<MyObj>(new MyObj()));

Я проверил реализацию MSFT и не вижу ничего очевидного - кто-нибудь сталкивался с этим и есть какие-нибудь идеи?

РЕДАКТИРОВАТЬ: Хорошо, чтобы быть немного болееясно / специфичны.Это явно проблема использования памяти Windows, и я, очевидно, что-то упускаю.Теперь я попробовал следующее:

  1. Создать std::list из 100000 MyObj
  2. Создать std::list из 100000 MyObj *
  3. Создать std::listиз 100000 int *
  4. Создайте std::list из 50000 int *

В каждом случае каждый добавочный член списка, будь то указатель или нет, раздувает мойприменение 4400 (!) байтов .Это в 64-разрядной версии, без любой отладочной информации, включенной (Linker> Debugging> Generate Debug Info, установленной в No).

Мне, очевидно, нужно немного больше изучить это, чтобы сузить его до небольшого контрольного примера.

Для тех, кто заинтересован, я определяю размер приложения с помощью Process Explorer .

Оказывается, это была целая фрагментация кучи.Как смешно.4400 байт на 8-байтовый объект!Я переключился на предварительное распределение, и проблема полностью ушла - я привык к некоторой неэффективности, полагаясь на распределение для каждого объекта, но это было просто смешно.

Реализация MyObj ниже:

class   MyObj
{
public:
    MyObj() { memset(this,0,sizeof(MyObj)); }

    double              m_1;
    double              m_2;
    double              m_3;
    double              m_4;
    double              m_5;
    double              m_6;
    double              m_7;
    double              m_8;
    double              m_9;
    double              m_10;
    double              m_11;           
    double              m_12;           
    double              m_13;
    double              m_14;
    double              m_15;
    double              m_16;
    double              m_17;
    double              m_18;
    double              m_19;
    double              m_20;
    double              m_21;
    double              m_22;
    double              m_23;
    CUnit*              m_UnitPtr;
    CUnitPos*           m_UnitPosPtr;
};

Ответы [ 3 ]

3 голосов
/ 28 ноября 2011

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

Например, это:

for(int i = 0; i < 100; ++i) {
  new int;
}

будет использовать больше памяти, чем это:

new int[100];

Несмотря на то, что выделенная сумма одинакова.


Edit:

Я получаю на 13% больше памяти, используемой unique_ptr, используя GCC в Linux.

2 голосов
/ 28 ноября 2011

Это не ответ, но он не помещается в комментарии и может быть иллюстративным.

Я не могу воспроизвести претензию (GCC 4.6.2).Возьмите этот код:

#include <memory>
#include <list>

struct Foo { char p[200]; };

int main()
{
  //std::list<Foo> l1(100);

  std::list<std::unique_ptr<Foo>> l2;
  for (unsigned int i = 0; i != 100; ++i) l2.emplace_back(new Foo);
}

Включение только l1 производит (в Valgrind):

total heap usage: 100 allocs, 100 frees, 20,800 bytes allocated

Включение только l2 и цикл дает:

total heap usage: 200 allocs, 200 frees, 21,200 bytes allocated

Интеллектуальные указатели занимают ровно 4 × 100 байт.

В обоих случаях /usr/bin/time -v дает:

Maximum resident set size (kbytes): 3136

Более того, pmap показывает в обоих случаях: total 2996K.Для подтверждения я изменил размер объекта на 20000, а количество элементов - на 10000. Теперь эти числа равны 198404K против 198484K: разница в 80000B, 8B на уникальный указатель (предположительно, в распределителе происходит выравнивание 8B)список).При тех же изменениях «максимальный размер резидентного набора», сообщаемый time -v, теперь равен 162768 против 164304.

2 голосов
/ 28 ноября 2011

std::list<MyObj> содержит N копий вашего объекта (+ информация, необходимая для указателей списка).

std::unique_ptr<MyObj> содержит указатель на экземпляр вашего объекта.(Он должен содержать только MyObj*).

Так что std::list<std::unique_ptr<MyObj>> не является прямым эквивалентом вашего первого списка.std::list<MyObj*> должен иметь тот же размер, что и список std::unque_ptr.

После проверки реализации единственной вещью, которая может быть встроена рядом с указателем самого объекта, может быть «удалитель», которыйв случае по умолчанию - пустой объект, который вызывает operator delete.

У вас есть отладка или сборка выпуска?

...