Почему реализация stc::string в libC++ занимает 3x памяти как libstdC++?

Рассмотрим следующую тестовую программу:

#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 ответа

Вот короткая программа, которая поможет вам изучить оба вида использования памяти: 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/ легко получить выходные данные для различных комбинаций компилятор /lib, например:

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, который является мерой того, сколько chars строка может содержать до того, как ей потребуется выделить новый, больший буфер из кучи. Для реализаций gcc-5.0, libC++ и VS-2015 это мера буфера коротких строк. То есть буфер размера, выделенный в стеке для хранения коротких строк, что позволяет избежать более дорогого выделения кучи.

Похоже, что реализация libC++ имеет наименьшее (использование стека) из реализаций коротких строк, и в то же время содержит самый большой из буферов коротких строк. И если вы посчитаете общее использование памяти (стек + куча), libC++ имеет наименьшее общее использование памяти для этой двухсимвольной строки среди всех 4 из этих реализаций.

Следует отметить, что все эти измерения были сделаны на 64-битных платформах. На 32-битной основе использование стека libC++ уменьшится до 12, а буфера небольшой строки уменьшится до 10. Я не знаю поведение других реализаций на 32-битных платформах, но вы можете использовать приведенный выше код, чтобы выяснить,,

Вы не должны беспокоиться, разработчики стандартной библиотеки знают, что они делают.

Используя последний код из магистрали subversion GCC libstdC++, вы получите следующие числа:

32 320 344

Это потому, что несколько недель назад я перешел по умолчанию std::string реализация для использования строковой оптимизации (с пробелом в 15 символов) вместо реализации копирования при записи, с которой вы тестировали.

Резюме: выглядит только так libstdc++ использует один char* , На самом деле он выделяет больше памяти.

Таким образом, вы не должны быть обеспокоены тем, что Кланг libc++ реализация памяти неэффективна.

Из документации libstdC++ (под подробным описанием):

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, чтобы получить указатель на заголовок.

Этот подход имеет огромное преимущество в том, что строковый объект требует только одного выделения. Все уродство ограничено одной парой встроенных функций, каждая из которых компилируется в одну инструкцию добавления: _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 определено. Если он определен, указатель данных будет выровнен по словам, если строка короткая. Для получения дополнительной информации см. Исходный код.

Оптимизация коротких строк позволяет избежать выделения кучи, поэтому она также выглядит дороже, чем libstdc++ реализация, если вы рассматриваете только те части, которые расположены в стеке. sizeof(std::string) показывает только использование стека, а не общее использование памяти (стек + куча).

Я не проверял фактические реализации в исходном коде, но я помню, как проверял это, когда работал над своей библиотекой C++. 24-байтовая строка является типичной реализацией. Если длина строки меньше или равна 16 байтам, вместо malloc'ing из кучи, она копирует строку во внутренний буфер размером 16 байт. В противном случае он размещает и хранит адрес памяти и т. Д. Эта незначительная буферизация фактически помогает с точки зрения производительности во время выполнения.

Для некоторых компиляторов есть возможность отключить внутренний буфер.

Другие вопросы по тегам