Почему реализация libС++ из std::string занимает 3-кратную память как libstdС++?
Рассмотрим следующую тестовую программу:
#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 раза больше памяти для простой программы. Как различается реализация, которая вызывает дисбаланс в памяти? Должен ли я быть обеспокоен и как мне его решить?
Ответы
Ответ 1
Вот краткая программа, которая поможет вам изучить использование двух типов памяти 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
лязг/LibС++:
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, libС++ и VS-2015 это мера буфера коротких строк. То есть буфер размера, выделенный в стеке, содержит короткие строки, тем самым избегая более дорогого распределения кучи.
Похоже, что реализация libС++ имеет наименьшее (использование стека) реализации коротких строк и все же содержит самый большой из коротких строковых буферов. И если подсчитать общее использование памяти (стек + куча), libС++ имеет наименьшее общее использование памяти для этой 2-символьной строки среди всех 4 этих реализаций.
Следует отметить, что все эти измерения были выполнены на 64-битных платформах. На 32-разрядном уровне использование стека libС++ будет уменьшаться до 12, а буфер с небольшой строкой - до 10. Я не знаю поведения других реализаций на 32-битных платформах, но вы можете использовать приведенный выше код, чтобы узнать.
Ответ 2
Вы не должны беспокоиться, разработчики стандартных библиотек знают, что они делают.
Используя последний код из соединительной линии GCC subversion libstdС++, вы получите следующие цифры:
32 320 344
Это связано с тем, что с нескольких недель назад я включил реализацию по умолчанию std::string
для использования оптимизации небольших строк (с пространством для 15 символов) вместо реализации копирования на запись, с которой вы протестировали.
Ответ 3
Сводка: он выглядит только как libstdc++
использует один char*
. Фактически, он выделяет больше памяти.
Итак, вы не должны беспокоиться о том, что реализация Clang libc++
неэффективна в памяти.
Из документации libstdС++ (под Подробное описание):
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_ALTERNATE_STRING_LAYOUT
).
Оптимизация коротких строк позволяет избежать распределения кучи, поэтому она также выглядит более дорогостоящей, чем реализация libstdc++
, если вы рассматриваете только части, выделенные в стеке. sizeof(std::string)
показывает только использование стека, а не общее использование памяти (стек + куча).
Ответ 4
Я не проверял фактические реализации в исходном коде, но я помню, как это проверял, когда я работал над своей строковой библиотекой С++. Обычная реализация строки в 24 байта. Если длина строки меньше или равна 16 байтам, вместо malloc'ing из кучи она копирует строку во внутренний буфер размером 16 байт. В противном случае он сохраняет и сохраняет адрес памяти и т.д. Эта небольшая буферизация фактически помогает с точки зрения производительности рабочего времени.
Для некоторых компиляторов есть возможность отключить внутренний буфер.