новый не вызван, но память выделена

Я написал простую реализацию Trie. Вот исходный код:

#include <string>
#include <map>

typedef unsigned int uint;

class Trie {
public:
    class Node {
    public:
            Node(const char & _value);
            ~Node();
            char get_value() const;
            void set_marker(const uint & _marker);
            uint get_marker() const;
            bool add_child(Node * _child);
            Node * get_child(const char & _value) const;
            void clear();
    private:
            char m_value;
            uint m_marker;
            std::map<char, Node *> m_children;
    };

    Trie();
    ~Trie();
    bool insert(const std::string & _str);
    bool find(const std::string & _str) const;
private:
    Node * m_root;
};
// - implementation (in a different file)
using namespace std;

Trie::Node::Node(const char & _value) :
            m_value(_value), m_marker(0), m_children() {
}

Trie::Node::~Node() {
    clear();
}

void Trie::Node::clear() {
    map<char, Node*>::const_iterator it;
    for (it = m_children.begin(); it != m_children.end(); ++it) {
            delete it->second;
    }
}

void Trie::Node::set_marker(const uint & _marker) {
    m_marker = _marker;
}

uint Trie::Node::get_marker() const {
    return m_marker;
}

char Trie::Node::get_value() const {
    return m_value;
}

Trie::Node * Trie::Node::get_child(const char & _value) const {
    map<char, Node*>::const_iterator it;
    bool found = false;
    for (it = m_children.begin(); it != m_children.end(); ++it) {
            if (it->first == _value) {
                    found = true;
                    break;
            }
    }
    if (found) {
            return it->second;
    }
    return NULL;
}

bool Trie::Node::add_child(Node * _child) {
    if (_child == NULL) {
            return false;
    }
    if (get_child(_child->get_value()) != NULL) {
            return false;
    }
    m_children.insert(pair<char, Node *>(_child->get_value(), _child));
    return true;
}

Trie::Trie() :
            m_root(new Node('\0')) {
}

Trie::~Trie() {
    delete m_root;
}

bool Trie::insert(const string & _str) {
    Node * current = m_root;
    bool inserted = false;
    for (uint i = 0; i < _str.size(); ++i) {
            Node * child = current->get_child(_str[i]);
            if (child == NULL) {
                    child = new Node(_str[i]);
                    current->add_child(child);
                    inserted = true;
            }
            current = child;
    }
    if (current->get_marker() != _str.size()) {
            current->set_marker(_str.size());
            inserted = true;
    }
    return inserted;
}

bool Trie::find(const std::string & _str) const {
    Node * current = m_root;
    bool found = false;
    for (uint i = 0; i < _str.size(); ++i) {
            Node * child = current->get_child(_str[i]);
            if (child == NULL) {
                    break;
            } else {
                    current = child;
            }
    }
    if (current->get_marker() == _str.size()) {
            found = true;
    }
    return found;
}

Вот моя тестовая программа:

#include <iostream>
#include <sstream>
#include "Trie.h"

int main() {
    Trie t;
    for (unsigned int i = 0; i < 10000; ++i) {
            t.insert("hello");
    }
    return 0;
}

Моя проблема в том, что, хотя «привет» уже вставлено при второй попытке его вставки и, таким образом, new больше не вызывается, выделяется и отменяется выделение большого количества памяти. Эта сумма увеличивается по мере того, как I увеличивает значение max i. Например, в приведенном выше случае valgrind дает следующий результат:

==10322== HEAP SUMMARY:
==10322==     in use at exit: 0 bytes in 0 blocks
==10322==   total heap usage: 10,011 allocs, 10,011 frees, 300,576 bytes allocated

Я подтвердил, что количество вызовов конструктора Node () постоянно. Тогда почему и как выделяется и освобождается вся эта память?


person Anupam Srivastava    schedule 08.01.2013    source источник
comment
Вы создаете множество карт. Они могут распределять память внутри.   -  person Bo Persson    schedule 08.01.2013


Ответы (2)


Каждый раз, когда вы вызываете insert, вы передаете ему const char[6], но он ожидает const std::string&, и поэтому каждая итерация создает временный std::string, который затем передается функции, а затем уничтожается перед следующей итерацией. Это проясняет 10000 выделений и освобождений, оставляя только 11, которые предположительно являются вашим выделением узла, а также все, что std::map делает внутри, и несколько других мест, которые я упустил (например, копии строк или карты)

Контейнер может выделять память, даже если он не содержит элементов, но я бы сказал, что он должен был быть спроектирован иначе, и был бы удивлен, если бы какая-либо крупная реализация контейнера сделала бы такую ​​вещь. (Хотя deque может быть исключением)

person Mooing Duck    schedule 08.01.2013

std::map будет динамически выделять свою собственную память, и вы создаете новую каждый раз, когда вызываете get_child(). Я не могу сказать, сколько памяти он выделяет при использовании конструктора по умолчанию, но, вероятно, это что-то. Тот факт, что вы не вызываете new, не означает, что другие типы, созданные вашим классом, этого не делают.

Кроме того, std::map не собирается выделять совершенно новое хранилище в куче для каждого вставленного элемента. Это было бы ужасно неэффективно. У него есть некоторый внутренний алгоритм для увеличения своего резервного хранилища, когда это необходимо, и он, безусловно, будет выделять больше, чем необходимо для размещения этого одного нового элемента.

person Ed S.    schedule 08.01.2013
comment
Не могли бы вы подтвердить это более подробно? Я просто просматриваю сохраненный std::map с помощью итераторов. - person Anupam Srivastava; 08.01.2013
comment
@anupamsr Каждый раз, когда вы вызываете Trie::Node::get_child(), вы создаете std::map в стеке: map<char, Node*> children; - person bames53; 08.01.2013
comment
@ bames53: Но распределения сообщаются в куче. Это мое замешательство. Медлительность в программе ощущается при большом количестве i. Даже после удаления этой строки я все равно получаю тот же объем распределения. - person Anupam Srivastava; 08.01.2013
comment
@anupamsr, создающий std::map в стеке, может привести к std::map распределению памяти в куче. - person bames53; 08.01.2013
comment
Я был бы удивлен, если бы std::map выделил что-нибудь, когда он не содержит элементов. Я бы поспорил, что каждое из этих распределений было std::string. - person Mooing Duck; 08.01.2013
comment
Кроме того, контейнеры STL используют оптимизацию пустых членов, поэтому они не будут выделять память в куче. - person Anupam Srivastava; 08.01.2013
comment
@anupamsr В этом нет никакого смысла. - person bames53; 09.01.2013
comment
@ bames53 Я имел в виду, что неинициализированный динамический контейнер STL не будет выделять память в куче, если к нему не добавлен элемент. Таким образом, children будет создан, но не будет занимать никакого хранилища, если к нему не будет добавлен элемент. - person Anupam Srivastava; 09.01.2013
comment
@anupamsr Ладно, в этом есть смысл. Но некоторые реализации контейнеров действительно выделяют в своем конструкторе по умолчанию. Например. см. слайды 41 и 43 здесь о deque и unordered_map в старая версия libstdc ++. - person bames53; 09.01.2013