У меня есть примерно 4 миллиона значений в файле, который я хочу сохранить в контейнере для выполнения вычислений.
Ключ каждого значения состоит из 2 целых чисел без знака. Значение представляет собой структуру, содержащую 4 числа двойной точности.
После загрузки значения не изменятся.
typedef pair<unsigned int, unsigned int> aa;
struct MyRecord { double a1; double a2; double a3; double a4; };
class MyRecordHash{
public:
size_t operator()(const aa &k) const{ return k.first * 10000 + k.second; }
};
struct MyRecordEquals : binary_function<const aa&, aa&, bool> {
result_type operator()( nm lhs, nm rhs ) const
{
return (lhs.first == rhs.first) && (lhs.second == rhs.second);
}
};
std::unordered_map<aa,MyRecord,MyRecordHash,MyRecordEquals> MyRecords;
Я использую MyRecords.reserve (number_of_records) перед вставкой записей.
Проблема A: Хотя я вызываю резерв перед тем, как начать вставку данных, выделенной памяти недостаточно, и по мере вставки данных происходит перераспределение все большего объема памяти. Разве не следует выделять необходимую память с резервом? Например, для 4-метровых записей он выделяет с резервом 38,9 МБ, а затем после вставки дополнительно 256,5 МБ.
Проблема B: процесс вставки довольно медленный. Я проверил коэффициент загрузки, он никогда не превышает 0,5. Есть ли какие-нибудь предложения проверить еще что-нибудь? Для прошивки использую MyRecords.insert.
Проблема C: После завершения расчетов я вызываю MyRecords.clear (). Вместо того, чтобы удалять содержимое «мгновенно», он начинает удалять запись за записью (примерно 3 Мб / сек). Если я не вызываю clear (), я получаю такое же поведение. Это нормально? Я проверил все предыдущие вопросы о stackoverflow, и единственное, что я нашел, это то, что это может быть связано с отладкой. Я использовал параметр -O3, но он ничего не изменил.
Я использую компилятор MinGW-64 версии 4.9.1.
Спасибо всем за то, что прочитали это и за ваши предложения.
ИЗМЕНИТЬ после предложенных комментариев и решений:
-Похоже, что нет способа освободить или предварительно выделить память STL для unordered_maps при использовании отличных от стандартных типов для ключа и содержащихся данных. -Метод Reserve резервирует память только для хэшей. -Использование вектора <> с индексами, вычисленными из ключа значений, работало очень хорошо. Просто предварительно выделите вектор, а затем, используя myvector.at () = value, установите значения. Деструктор по умолчанию освобождает вектор почти мгновенно (со значениями 4 м требуется 2-3 секунды, а не 5 минут с unordered_map). -Использование памяти с вектором меньше, так как ключ не хранится -Случайный доступ к вектору кажется немного медленнее, но еще не профилировал код.
Еще раз спасибо всем за помощь.
3 ответа
Взято из комментариев ...
Думаю, я задам вопрос (чтобы взглянуть на вещи в другом свете), вы уверены, что вам нужен ассоциативный контейнер?
Если у вас есть записи, охватывающие почти все комбинации клавиш, тогда, возможно, подойдет вектор, если вы не против тратить немного места на неиспользуемые записи. Поэтому относитесь к своим ключам как к индексам вашего вектора. Это даст вам постоянный поиск по времени и позволит вам заранее выделить всю необходимую память, избегая затрат на множественные выделения.
Ценность этого подхода будет зависеть от распределения ключей в пространстве ключей и от того, насколько легко они могут быть сопоставлены с индексом массива, начинающимся с нуля.
Если вы попробуете этот подход, мне было бы очень интересно посмотреть, как он работает по сравнению с тем, что вы делаете сейчас.
reserve
, вероятно, выделяет место только для хеш-структуры (например, вектора указателей на данные), а не для самих данных.
Возьмите ваш пример вставки 4M записей. Каждая запись - 4 двойных или 4 * 8 байтов. 4 млн записей означают 4 * 8 * 4 = 128 МБ данных. Таким образом, очевидно, что выделения 38,9 Мбайт Reserve () недостаточно.
Все, что делает unordered_map::reserve
, - это увеличивает количество сегментов, чтобы вы не превысили максимальный коэффициент загрузки при вставке указанного количества элементов. Это тебе не поможет.
unordered_map
- это контейнер на основе узлов; в результате каждая вставка - это отдельное выделение. Деструкторы вашей структуры данных тривиальны, но освобождение 4 миллионов блоков памяти довольно дорого.
Вы можете
- Используйте настраиваемый распределитель, который эффективно обрабатывает ваш шаблон распределения,
- или переключитесь на другую структуру данных.
boost::flat_map
- хороший выбор (и немного увеличенная временная сложность вполне может быть компенсирована увеличением производительности за счет лучшей локальности данных).
Похожие вопросы
Новые вопросы
c++
C ++ - это язык программирования общего назначения. Первоначально он был разработан как расширение C и имеет аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде (который должен быть) скомпилирован с помощью компилятора C ++. Используйте тег для конкретной версии для вопросов, связанных с конкретной версией стандарта [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] или [C ++ 23] и т. Д. .