Как сгенерировать хэш-карту для огромного объема данных?

#c #data-structures #hashtable

#c #структуры данных #хэш-таблица

Вопрос:

Я хочу создать карту таким образом, чтобы набор указателей указывал на массивы динамического размера. Я использовал хеширование с цепочкой. Но поскольку данные, для которых я их использую, огромны, программа выдает std::bad_alloc после нескольких итераций. Причина, по которой может быть new использован для создания связанного списка.

Кто-нибудь, пожалуйста, подскажите, какую структуру данных мне использовать? Или что-нибудь еще, что может улучшить использование памяти с помощью моей хэш-таблицы?

Программа написана на C .

Вот как выглядит мой код: инициализация хэш-таблицы:

 class Link
{ 
  public:
         double iData; 
         Link* pNext; 
         Link(double it) : iData(it) 
         { }
         void displayLink()
         { cout << iData << " "; }
}; 

class List
 {
  private:
          Link* pFirst; 
  public:
         List() 
         { pFirst = NULL; }
         void insert(double key) 
         {

           if(pFirst==NULL)
           pFirst = new Link(key);
       else
          {
        Link* pLink = new Link(key);
        pLink->pNext = pFirst;
        pFirst = pLink;
       }

         }     

 }; 
class HashTable
{      
  public:
         int arraySize;
         vector<List*> hashArray; 

         HashTable(int size) 
         {

            hashArray.resize(size); 
            for(int j=0; j<size; j  ) 
            hashArray[j] = new List; 
         }
};
  

основной фрагмент:

 int t_sample = 1000;
 for(int i=0; i < k; i  )                                // initialize random position
{
        x[i] = (cal_rand() * dom_sizex);   //dom_sizex = 20e-10  cal_rand() generates rand no between 0 and 1
        y[i] = (cal_rand() * dom_sizey);    //dom_sizey = 10e-10
}

for(int t=0; t < t_sample; t  )
{
 int size;
 size = cell_nox * cell_noy; //size of hash table cell_nox = 212, cell_noy = 424

 HashTable theHashTable(size); //make table
 int hashValue = 0;

 for(int n=0; n<k; n  )   // k = 10*212*424
 {
  int m = x[n] /cell_width;     //cell_width = 4.7e-8
  int l = y[n] / cell_width;

   hashValue = (kx*l) m;
   theHashTable.hashArray[hashValue]->insert(n); 

  }

   -------
   -------
 }
  

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

1. Итак, вам нужно всего хранить ~ 900 миллионов значений в памяти? Даже если это всего лишь 4 байта на значение, у вас закончится доступное виртуальное адресное пространство для 32-разрядной программы. Никакое изменение структуры данных не может это исправить.

2. @T.C. но люди выполняют такие симуляции на Fortran. Нет ли какой-либо возможности справиться с этим?

3. @aks: хорошо, вы могли бы прокомментировать, нужно ли вам сохранять все значения из каждой итерации одновременно или есть какой-то способ обработки и удаления некоторых из этих результатов ранее? В зависимости от ваших потребностей в доступе к данным вы можете рассмотреть возможность настройки хорошего объема пространства подкачки или явной записи значений на диск до тех пор, пока они снова не понадобятся. Кроме того, вы можете скомпилировать 64-разрядное приложение? Сколько оперативной памяти у вас есть в наличии?

4. @aks Итак, у вас действительно есть только ~ 900 тыс. значений, и вы обновляете их только на каждой итерации, а не создаете новые? Тогда вам, вероятно, нужно искать утечки памяти в вашем коде.

5. Как говорит T.C., если он проходит через несколько итераций, а затем выходит из строя, это говорит о том, что у него было достаточно памяти для начальной итерации, но впоследствии произошла утечка. В более общем плане, std::unordered_map<key, std::vector<value>> звучит примерно так, если у вас нет, например, непрерывных увеличивающихся ключей — тогда вы можете просто иметь vector<value> . Если разница между минимальной и максимальной длиной очень мала, вы можете рассмотреть a std::array<> с начальным элементом длины или завершающим элементом sentinel .

Ответ №1:

Перво-наперво, используйте стандартный контейнер. В вашем конкретном случае вы можете захотеть:

  • либо std::unordered_multimap<int, double>
  • или std::unordered_map<int, std::vector<double>>

(Примечание: если у вас нет C 11, они доступны в Boost)

Ваш основной цикл становится (используя второй вариант):

 typedef std::unordered_map<int, std::vector<double>> HashTable;

for(int t = 0; t < t_sample;   t)
{
    size_t const size = cell_nox * cell_noy;
       // size of hash table cell_nox = 212, cell_noy = 424

    HashTable theHashTable;
    theHashTable.reserve(size);

    for (int n = 0; n < k;   n)   // k = 10*212*424
    {
        int m = x[n] / cell_width;     //cell_width = 4.7e-8
        int l = y[n] / cell_width;

        int const cellId = (kx*l) m;

        theHashTable[cellId].push_back(n);
    }
}
  

Это не приведет к утечке памяти (надежно), хотя, конечно, у вас могут быть другие утечки, и, таким образом, даст вам надежную базовую линию. Это также, вероятно, быстрее, чем ваш подход, с более удобным интерфейсом и т.д…

В общем, вам не следует заново изобретать колесо, если только у вас нет конкретной потребности, которая не удовлетворяется доступными колесами, или вы на самом деле пытаетесь узнать, как создать колесо или создать лучшее колесо.

Ответ №2:

ОС должна решать те же проблемы со страницами памяти, может быть, стоит посмотреть, как это делается? Прежде всего, давайте предположим, что все страницы находятся на диске. Страница — это фрагмент памяти фиксированного размера. Для вашего варианта использования, допустим, это массив ваших записей. Поскольку объем оперативной памяти ограничен, ОС поддерживает сопоставление между номером страницы и ее местоположением в оперативной памяти.

Итак, допустим, на ваших страницах 1000 записей, и вы хотите получить доступ к записи 2024, вы должны запросить у ОС страницу 2 и прочитать запись 24 с этой страницы. Таким образом, ваша карта имеет размер всего 1/1000.

Теперь, если ваша страница не имеет сопоставления с ячейкой памяти, то она либо находится на диске, либо к ней никогда раньше не обращались (пуста). Затем вам нужно заменить другую страницу и загрузить эту страницу с диска (и обновить отображение местоположения).

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

Дело в том,:

Что это значит для вас?

Во-первых, ваши данные превышают объем оперативной памяти — вы не обойдетесь без записи на диск, если не хотите сначала попробовать сжатие. Во-вторых, ваши цепочки могут работать как страницы, если хотите, но мне интересно, будет ли лучше работать просто подкачка вашего хэш-кода. Я имею в виду, что используйте верхние биты в качестве номера страницы, а младшие биты — в качестве смещения на странице. По-прежнему важно избегать столкновений, так как вы хотите загрузить как можно меньше страниц. Вы все равно можете объединить свои страницы в цепочку и в итоге получить карту гораздо меньшего размера. Вторая важная часть — решить, какие страницы поменять местами, чтобы освободить место для новых страниц. LRU должен работать нормально. Если вы можете лучше предсказать, какие страницы вам (не) понадобятся, тем лучше для вас. В-третьих, вам нужны заполнители для ваших страниц, чтобы указать, находятся ли они в памяти или на диске.

Надеюсь, это поможет.

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

1. Извините, но большая часть этого отскочила от моей головы. Позвольте мне перефразировать мой вопрос. Просто знайте, что я на самом деле пытаюсь сделать со своим кодом. У меня 8988880 частиц, 89888 ячеек. Итак, 10 частиц в каждой ячейке. Теперь, поскольку я случайным образом распределяю позицию для этих частиц, мне нужно проиндексировать каждую частицу в ее ячейке. Итак, я сопоставил 10 значений с ячейкой, используя связанный список. И ячейки отмечены хэш-значениями хэш-таблицы. Когда я смоделировал эту систему с более чем 200 итерациями, программа завершилась следующим образом: terminate вызывается после создания экземпляра ‘std::bad_alloc’

2. Итак, что это за ячейки, пространственные позиции / объемы? Зачем вообще использовать хэш-таблицы, простого массива недостаточно? Из того, что я понял из вашего кода, ваши итерации независимы друг от друга? Разве вы не можете просто сохранять результаты каждой итерации на диск перед началом следующей и удалять их из памяти?

3. вы имеете в виду, что я должен инициализировать 89888 массивов? я использовал это для удаления: for(int m=0; m () { Link pCurrent = pFirst; while (pCurrent != NULL) { Link* del = pCurrent; pCurrent = pCurrent-> pNext; свободно (del); } pFirst = NULL; }

4. Кроме того, всегда ли в ячейке 10 частиц или это просто ожидаемое значение? Удаление выглядит нормально для меня, но, честно говоря, у меня больше опыта работы с Java.

5. ни один 10 не является средним. он будет меняться от ячейки к ячейке.