Почему std::vector::operator= перераспределяет память?

#c #string #stl

#c #строка #stl

Вопрос:

Я расследую случай, когда потребляется намного больше памяти, чем необходимо. Если я присваиваю строку an std::vector , она внезапно резервирует больше памяти кучи, чем необходимо, хотя размер строки уже известен:

Вот к чему я это сводил:

 #include <vector>
#include <iostream>
#include <new>

void* operator new(size_t size) { 
    void * p = malloc(size); 
    std::cout << "talloc " << size << " @ " << p; 
    return p;
} 

void operator delete(void* p) { 
    std::cout << "t      free " << p; 
    free(p);
} 

int main() {
    {
        std::cout << std::endl << "1. Create first string:   ";
        auto s1 = std::string{"String with 20 chars"};

        std::cout << std::endl << "2. Create longer string:  ";
        auto s2 = std::string{"String with 25 characters"};

        std::cout << std::endl << "3. Copy construct:        ";
        auto s3 = s2;

        std::cout << std::endl << "4. Copy assign:           ";
        s1 = s3;

        std::cout << std::endl << "5. Leaving scope:         ";
    }
    std::cout << std::endl;
}
 

Результат:

 1. Create first string:     alloc 21 @ 0x56047f176280
2. Create longer string:    alloc 26 @ 0x56047f1762a0
3. Copy construct:          alloc 26 @ 0x56047f1762d0
4. Copy assign:             alloc 41 @ 0x56047f176300         free 0x56047f176280
5. Leaving scope:                 free 0x56047f1762d0         free 0x56047f1762a0         free 0x56047f176300
 

Я бы ожидал, что строка 4 будет такой же, как строка 3.

Почему как libstdc (этот результат), так и libc (32/48 байт) выделяют больше памяти для назначения копирования, чем для построения копии?В обоих случаях новый размер известен. Я не вижу, как один из них, скорее всего, потребует дополнительной памяти в будущем.

Комментарии:

1. Виртуальная память дешевая (в большинстве случаев бесплатная). Выделение памяти вам ничего не стоит, пока кто-то не использует эту память. Например, Linux не поддерживает выделение физической памяти до того, как кто-то записывает. Итак, выделение 128 ГБ ничего не стоит, если вы записываете только в 1 МБ.

2. Исправьте свой вопрос.

3. @JesperJuhl: Конечно, но это другой масштаб. Если каждая строка будет стоить вам 41 вместо 26 байт, это пространство будет выделено, поскольку оно намного меньше страницы. В моем случае (база данных в памяти) это привело к потере 300 МБ на наборе данных в 1 ГБ.

4. @Yakk-AdamNevraumont — Не могли бы вы уточнить?

Ответ №1:

Я отследил это в реализации basic_string libstdc . operator=(const basic_stringamp;) вызовы this->assign , какие вызовы _M_assign , какие вызовы _M_create с новой (минимальной) и старой пропускной способностью. Здесь исходная емкость удваивается. Ссылка на политику экспоненциального роста включена в качестве комментария.

Во-первых, я не понял, как это связано с моим примером. В конце концов, я просто хочу заменить значение.

Политика роста для operator=(const basic_stringamp;) имеет больше смысла, когда вместо отдельного назначения вы рассматриваете этот код:

 std::string s;
for (auto i = 100; i < 110;   i) s = s   std::to_string(i);
 

Здесь установка емкости так, чтобы она была «просто подходящей», нарушила бы требование линейного роста.

Чтобы решить проблему потребления памяти, я теперь вызываю shrink_to_fit() целевую строку.