Как измерять использование памяти std:: unordered_map
Мы знаем, что реализация контейнеров на основе хэш-таблиц, таких как std::unordered_map
, использует много памяти, но я не знаю, сколько стоит?
Помимо обозначений пространственной сложности и не учитывая, является ли контейнерный элемент указателем на более крупный объект:
Есть ли способ выяснить, сколько байтов используется таким контейнером во время выполнения?
Можно ли во время выполнения указать, сколько памяти использует какой-либо контейнер?
Ответы
Ответ 1
Если вы хотите получить приблизительный размер, я думаю, что bucket_count()
и max_load_factor()
достаточно, что дает текущее количество ведер и коэффициент загрузки.
Rational:
-
Если load_factor
<= 1, это означает, что bucket_count()
>= элементы на карте, тогда bucket_count() - это размер использования памяти.
-
Если load_factor
> 1, то bucket_count()
* load_factor
указывает максимальный элемент на карте. Обратите внимание, что это максимальный размер, а не реальный размер.
Итак, для грубой памяти использование может выглядеть так:
unsigned n = mymap.bucket_count();
float m = mymap.max_load_factor();
if (m > 1.0) {
return n * m;
}
else {
return n;
}
Если вы хотите получить точное использование памяти, вам может понадобиться подсчитать все ведра, чтобы увидеть, сколько элементов в нем:
size_t count = 0;
for (unsigned i = 0; i < mymap.bucket_count(); ++i) {
size_t bucket_size = mymap.bucket_size(i);
if (bucket_size == 0) {
count++;
}
else {
count += bucket_size;
}
}
Ответ 2
Нет никакого переносного способа рассказать, сколько байтов используется. Что вы можете узнать, это:
-
size()
указывает, сколько элементов данных было вставлено в контейнер
-
bucket_count()
указывает, сколько ведер содержит базовую хеш-таблицу, каждый из которых может ожидать размещения связанного списка с связанными элементами.
Сейчас:
-
байты, фактически используемые для хранения элементов, будут m.size() * sizeof(M::value_type)
-
байты, используемые для ведрах хэш-таблицы, зависят от способа хранения внутренних списков - std::unordered_map::bucket_size
имеет постоянную сложность, поэтому мы можем разумно предположить, что будет указатель size()
и указатель головы на каждый ковш, поэтому m.bucket_count() * (sizeof(size_t) + sizeof(void*))
является разумной догадкой, хотя может быть, что только постоянная амортизированная сложность из-за ограниченности load_factor()
и отсутствия size
для каждого ведра (я бы предпочел реализовать ее таким образом самостоятельно)
-
если каждый из вставленных элементов является частью списка, ему понадобится указатель next
, поэтому мы можем добавить еще один m.size() * sizeof(void*)
-
каждое распределение памяти может быть округлено до размера, удобного для управления библиотекой распределения памяти. следующая мощность в два, что приближается к 100% наихудшему случаю неэффективности и 50% в среднем, поэтому пусть добавляет 50%, только для узлов списка, так как ведра, вероятно, обладают двумя заданными size_t
и указателем: 50% * size() * (sizeof(void*) + sizeof((M::value_type))
-
особенно в режиме отладки, может быть любое количество данных, связанных с реализацией и обнаружением ошибок
Вы можете изучить это, создав несколько больших таблиц и посмотрев, как top
или диспетчер процессов сообщает о различном использовании памяти.