为什么libc ++的std :: string实现占用了3x内存作为libstdc ++?

时间:2014-12-24 03:28:30

标签: c++ string libstdc++ libc++

考虑以下测试程序:

#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::cout << sizeof(std::string("hi")) << " ";
    std::string a[10];
    std::cout << sizeof(a) << " ";
    std::vector<std::string> v(10);
    std::cout << sizeof(v) + sizeof(std::string) * v.capacity() << "\n";
}

libstdc++libc++的输出分别为:

8 80 104
24 240 264

如您所见,libc++占用的内存是简单程序的3倍。实现有何不同会导致这种内存差异?我需要关注,我该如何解决它?

4 个答案:

答案 0 :(得分:48)

这是一个简短的程序,可以帮助您探索std::string:堆栈和堆的两种内存使用情况。

#include <string>
#include <new>
#include <cstdio>
#include <cstdlib>

std::size_t allocated = 0;

void* operator new (size_t sz)
{
    void* p = std::malloc(sz);
    allocated += sz;
    return p;
}

void operator delete(void* p) noexcept
{
    return std::free(p);
}

int
main()
{
    allocated = 0;
    std::string s("hi");
    std::printf("stack space = %zu, heap space = %zu, capacity = %zu\n",
     sizeof(s), allocated, s.capacity());
}

使用http://melpon.org/wandbox/可以很容易地获得不同编译器/库组合的输出,例如:

gcc 4.9.1:

stack space = 8, heap space = 27, capacity = 2

gcc 5.0.0:

stack space = 32, heap space = 0, capacity = 15

<强>铛/的libc ++:

stack space = 24, heap space = 0, capacity = 22

<强> VS-2015:

stack space = 32, heap space = 0, capacity = 15

(最后一行来自http://webcompiler.cloudapp.net

上面的输出还显示了capacity,它衡量了字符串在必须从堆中分配新的更大缓冲区之前可以容纳多少char个。对于gcc-5.0,libc ++和VS-2015实现,这是短字符串缓冲区的度量。也就是说,在堆栈上分配的大小缓冲区用于保存短字符串,从而避免了更昂贵的堆分配。

似乎libc ++实现具有最短(堆栈使用)的短字符串实现,但是包含最大的短字符串缓冲区。如果计算内存使用量(堆栈+堆),则libc ++在所有这4个实现中的这个2字符字符串的总内存使用量最小。

应该注意的是,所有这些测量都是在64位平台上进行的。在32位上,libc ++堆栈使用率将降至12,小字符串缓冲区降至10.我不知道32位平台上其他实现的行为,但您可以使用上面的代码找出来。

答案 1 :(得分:10)

您不应该担心,标准库实现者知道他们在做什么。

使用GCC subversion trunk libstdc ++中的最新代码给出了这些数字:

32 320 344

这是因为几个星期前我将默认的std::string实现切换为使用小字符串优化(空格为15个字符)而不是您测试的写时复制实现。

答案 2 :(得分:7)

摘要:libstdc++看起来只使用一个char*。实际上,它会分配更多内存。

所以,你不应该担心Clang的libc++实现内存效率低。

来自libstdc ++的文档(在Detailed Description下):

A string looks like this:

                                        [_Rep]
                                        _M_length
   [basic_string<char_type>]            _M_capacity
   _M_dataplus                          _M_refcount
   _M_p ---------------->               unnamed array of char_type
  

_M_p指向字符串中的第一个字符,然后将其转换为指向_Rep的指针并减去1以获取指向标题的指针。

     

这种方法具有巨大的优势,即字符串对象只需要一次分配。所有的丑陋都局限在一对内联函数中,每个内联函数都编译为一个add指令:_Rep :: _ M_data()和string :: _ M_rep();获取一个原始字节块并具有足够空间的分配函数,并在前面构造一个_Rep对象。

     

您希望_M_data指向字符数组而不是_Rep的原因是调试器可以看到字符串内容。 (可能我们应该添加一个非内联成员来获取调试器使用的_Rep,这样用户就可以检查实际的字符串长度。)

所以,它看起来只有一个char*,但这在内存使用方面有误导性。

以前libstdc++基本上使用了这种布局:

  struct _Rep_base
  {
    size_type               _M_length;
    size_type               _M_capacity;
    _Atomic_word            _M_refcount;
  };

这更接近libc++的结果。

libc++使用“短字符串优化”。确切的布局取决于是否定义了_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT。如果已定义,则如果字符串很短,则数据指针将是字对齐的。有关详细信息,请参阅source code

短字符串优化避免了堆分配,因此如果您只考虑在堆栈上分配的部分,它看起来比libstdc++实现成本更高。 sizeof(std::string)仅显示堆栈使用情况,而不是整体内存使用情况(堆栈+堆)。

答案 3 :(得分:2)

我没有检查源代码中的实际实现,但我记得在我处理C ++字符串库时检查这个。典型的是24字节字符串实现。如果字符串的长度小于或等于16个字节,则它将字符串复制到大小为16字节的内部缓冲区,而不是从堆中进行malloc。否则,它会mallocs并存储内存地址等。这种次要缓冲实际上有助于提高运行时间性能。

对于某些编译器,可以选择关闭内部缓冲区。