std::unique_ptr是底层对象的两倍大

std::unique_ptr twice as big as underlying object

本文关键字:两倍 unique ptr std 对象      更新时间:2023-10-16

我遇到了std::unique_ptrs的问题(特别是MSFT VS 10.0实现(。当我创建一个它们的std::列表时,我使用的内存是创建一个仅包含底层对象的std::列表时的两倍(注意:这是一个大对象——大约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. 创建100000 MyObj的std::list
  2. 创建100000 MyObj*的std::list
  3. 创建100000 int*的std::list
  4. 创建50000 int的std::list*

在每种情况下,列表中的每个add'l成员,无论是指针还是其他成员,都会使我的应用程序膨胀4400(!(字节。这是一个64位版本,不包含任何调试信息(Linker>debugging>Generate Debug Info设置为No(。

我显然需要对此进行更多的研究,以将其缩小到一个较小的测试用例中。

对于那些感兴趣的人,我将使用Process Explorer来确定应用程序的大小。

事实证明,这完全是堆碎片。真可笑。每8字节对象4400字节!我改为预分配,问题完全消失了——我习惯了依赖每个对象分配的低效率,但这太荒谬了。

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;
};

增加的内存可能是由于堆效率低下——由于内部碎片和malloc数据,您必须为分配的每个块支付额外的费用。您执行的分配量是将产生惩罚命中的分配量的两倍。

例如:

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

将使用比以下更多的内存:

new int[100];

即使分配的金额是相同的。


编辑:

在Linux上使用GCC使用unique_ptr时,我使用的内存增加了约13%。

std::list<MyObj>包含对象的N个副本(+列表指针所需的信息(。

std::unique_ptr<MyObj>包含一个指向对象实例的指针。(它应该只包含一个MyObj*(。

因此,std::list<std::unique_ptr<MyObj>>并不直接等同于您的第一个列表。std::list<MyObj*>应该给出与std::unque_ptr列表相同的大小。

在验证了实现之后,唯一可以嵌入对象本身指针旁边的东西可能是"deleter",在默认情况下,它是一个调用operator delete的空对象。

您有调试或发布版本吗?

这不是一个答案,但它不适合作为注释,而且可能是说明性的。

我无法复制索赔(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。现在的数字是198404K198484K:正好有80000B的差异,每个唯一指针8B(可能在列表的分配器中有一些8B对齐(。在相同的变化下,time -v报告的"最大驻留集大小"现在是162768164304