9

我遇到了 std::unique_ptrs 的问题(特别是 MSFT VS 10.0 的实现)。当我创建它们的 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::list100000 MyObj
  2. 创建一个std::list100000 MyObj*
  3. 创建一个std::list100000 int*
  4. 创建一个std::list50000 int*

在每种情况下,列表的每个 add'l 成员,无论是指针还是其他,都会使我的应用程序膨胀4400(!) bytes。这是一个 64 位版本的版本,不包含任何调试信息(链接器 > 调试 > 生成调试信息设置为否)。

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

对于那些感兴趣的人,我正在使用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;
};
4

3 回答 3

3

增加的内存可能来自堆效率低下 - 由于内部碎片和 malloc 数据,您必须为分配的每个块支付额外费用。您执行的分配量是会受到惩罚的两倍。

例如,这个:

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

将使用比这更多的内存:

new int[100];

即使分配的金额相同。


编辑:

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

于 2011-11-28T16:39:27.223 回答
2

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

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

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

在验证实现之后,唯一可以嵌入到对象本身指针旁边的东西可能是“删除器”,在默认情况下,它是一个调用operator delete.

您有 Debug 还是 Release 版本?

于 2011-11-28T16:37:01.523 回答
2

这不是一个答案,但它不适合评论,它可能是说明性的。

我无法重现该声明(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。现在数字是198404Kvs 198484K:正好 80000B 的差异,每个唯一指针 8B(大概在列表的分配器中进行了一些 8B 对齐)。在相同的变化下,time -v现在报告的“最大驻留集大小”是162768vs 164304

于 2011-11-28T16:40:45.143 回答