У меня есть примерно 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). -Использование памяти с вектором меньше, так как ключ не хранится -Случайный доступ к вектору кажется немного медленнее, но еще не профилировал код.

Еще раз спасибо всем за помощь.

2
Nonen 10 Мар 2015 в 20:37

3 ответа

Лучший ответ

Взято из комментариев ...


Думаю, я задам вопрос (чтобы взглянуть на вещи в другом свете), вы уверены, что вам нужен ассоциативный контейнер?

Если у вас есть записи, охватывающие почти все комбинации клавиш, тогда, возможно, подойдет вектор, если вы не против тратить немного места на неиспользуемые записи. Поэтому относитесь к своим ключам как к индексам вашего вектора. Это даст вам постоянный поиск по времени и позволит вам заранее выделить всю необходимую память, избегая затрат на множественные выделения.

Ценность этого подхода будет зависеть от распределения ключей в пространстве ключей и от того, насколько легко они могут быть сопоставлены с индексом массива, начинающимся с нуля.

Если вы попробуете этот подход, мне было бы очень интересно посмотреть, как он работает по сравнению с тем, что вы делаете сейчас.

0
Lionel 13 Мар 2015 в 08:24

reserve, вероятно, выделяет место только для хеш-структуры (например, вектора указателей на данные), а не для самих данных.

Возьмите ваш пример вставки 4M записей. Каждая запись - 4 двойных или 4 * 8 байтов. 4 млн записей означают 4 * 8 * 4 = 128 МБ данных. Таким образом, очевидно, что выделения 38,9 Мбайт Reserve () недостаточно.

0
nimrodm 10 Мар 2015 в 17:50

Все, что делает unordered_map::reserve, - это увеличивает количество сегментов, чтобы вы не превысили максимальный коэффициент загрузки при вставке указанного количества элементов. Это тебе не поможет.

unordered_map - это контейнер на основе узлов; в результате каждая вставка - это отдельное выделение. Деструкторы вашей структуры данных тривиальны, но освобождение 4 миллионов блоков памяти довольно дорого.

Вы можете

  • Используйте настраиваемый распределитель, который эффективно обрабатывает ваш шаблон распределения,
  • или переключитесь на другую структуру данных. boost::flat_map - хороший выбор (и немного увеличенная временная сложность вполне может быть компенсирована увеличением производительности за счет лучшей локальности данных).
2
T.C. 11 Мар 2015 в 06:46