У меня есть симуляция с N частицами, бегущими по T временным шагам. На каждом временном шаге каждая частица вычисляет некоторые данные о себе и других соседних частицах (в пределах радиуса), которые упакованы битами в c-строку длиной 4-22 байта (в зависимости от того, сколько существует соседних частиц). Я называю это государственной строкой.
Мне нужно посчитать, сколько раз встречается каждая строка состояния, чтобы сформировать гистограмму. Я пытался использовать Google Sparse Hash Map, но накладные расходы памяти сумасшедшие.
Я провел несколько сокращенных тестов (прилагается) более 100 000 временных шагов, для 500 частиц. В результате получается более 18,2 млн уникальных строк состояния из 50 млн возможных строк состояния, что соответствует фактической работе, которую необходимо выполнить.
В итоге используется 323 МБ свободного пространства для символа * и int для каждой уникальной записи, а также сама строка фактического состояния. Однако диспетчер задач сообщает об использовании 870M. Это 547 млн. Служебных данных, или ~ 251,87 бит / запись, по сравнению с тем, что Google рекламирует примерно в 4-5 битах.
Поэтому я полагаю, что я должен делать что-то не так. Но потом я нашел это сайт, который показал аналогичные результаты, однако, я не уверен, что его диаграммы показывают только размер хеш-таблицы, или включают в себя размер фактических данных. Кроме того, его код не освобождает какие-либо строки, вставляемые в уже существующую хеш-карту (то есть, если его диаграммы включают в себя размер фактических данных, он будет закончен).
Вот некоторый код, показывающий проблему с выводом:
#include <google/sparse_hash_map>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
//String equality
struct eqstrc
{
bool operator()(const char* s1, const char* s2) const
{
return (s1 == s2) || (s1 && s2 && !strcmp(s1,s2));
}
};
//Hashing function
template <class T>
class fnv1Hash
{
public:
size_t operator()(const T& c) const {
unsigned int hash = 2166136261;
const unsigned char *key = (const unsigned char*)(c);
size_t L = strlen((const char*)c);
size_t i = 0;
for(const unsigned char *s = key; i < L; ++s, ++i)
hash = (16777619 * hash) ^ (*s);
return (size_t)hash;
}
};
//Function to form new string
char * new_string_from_integer(int num)
{
int ndigits = num == 0 ? 1 : (int)log10((float)num) + 1;
char * str = (char *)malloc(ndigits + 1);
sprintf(str, "%d", num);
return str;
}
typedef google::sparse_hash_map<const char*, int, fnv1Hash<const char*>, eqstrc> HashCharMap;int main()
{
HashCharMap hashMapChar;
int N = 500;
int T = 100000;
//Fill hash table with strings
for(int k = 0; k < T; ++k)
{
for(int i = 0; i < N; ++i)
{
char * newString = new_string_from_integer(i*k);
std::pair<HashCharMap::iterator, bool> res = hashMapChar.insert(HashCharMap::value_type(newString, HashCharMap::data_type()));
(res.first)->second++;
if(res.second == false) //If the string already in hash map, don't need this memory
free(newString);
}
}
//Count memory used by key
size_t dataCount = 0;
for(HashCharMap::iterator hashCharItr = hashMapChar.begin(); hashCharItr != hashMapChar.end(); ++hashCharItr)
{
dataCount += sizeof(char*) + sizeof(unsigned int); //Size of data to store entries
dataCount += (((strlen(hashCharItr->first) + 1) + 3) & ~0x03); //Size of entries, padded to 4 byte boundaries
}
printf("Hash Map Size: %lu\n", (unsigned long)hashMapChar.size());
printf("Bytes written: %lu\n", (unsigned long)dataCount);
system("pause");
}
Hash Map Size: 18218975
Bytes written: 339018772
Peak Working Set (Reported by TaskManager): 891,228 K
Overhead: 560,155 K, or 251.87 bits/entry
Я пробовал Google Sparse Hash Map v1.10 и v2.0.2.
Я делаю что-то не так при использовании хэш-карты. Или есть лучший способ приблизиться к этому, потому что с этими строками я почти так же хорошо бы просто сохранил список строк, отсортировал их, а затем посчитал последовательные записи.
Спасибо за любую помощь
Поскольку меня спросили, вот формат фактических данных:
Каждый компонент имеет размер 2 байта и разбит на две части. 12бит и 4 бита.
Углы аппроксимируются (делятся на 16), для хранения в 4 битах.
Это немного многословно, поэтому я напишу пример:
0x120A 0x001B 0x136F
= Частица 288 (0x120
), с углом 10 (0xA
). Имел угол 11 (0xB
) в предыдущем временном шаге. Взаимодействует с 1 (0x001
) другая частица. Эта другая частица является частицей 310 (0x136
) и имел угол 15 (0xF
) в предыдущем временном шаге.
Частицы взаимодействуют с от 0 до 9 другими частицами, следовательно, 4-22 байта, которые я упомянул выше (хотя, редко, могут взаимодействовать с до 12 или более другими частицами. Нет предела. Если все 500 частиц находятся в радиусе, то строка будет длиной 1004 байта)
Дополнительная информация: Хеширующая функция и функция сравнения в моем фактическом коде используют размер, сохраненный в 12 старших значащих битах второго короткого замыкания, для выполнения обработки, поскольку в моих строках состояния могут появляться нетерминальные значения 0x0000. Это все работает отлично.
Эти цифры взяты из экспериментов с gcc на Linux. Для выделения коротких фрагментов размером 4–22 байта требуется 16 байтов для длины от 1 до 12, 24 байта для 13–20 и 32 байта для остальных.
Это означает, что для эксперимента со строками 18218975 («0» .. «50000000») требуется 291503600 байт в куче, а сумма их длин (плюс конечный 0) равна 156681483.
Таким образом, у вас есть 135 МБ служебных данных просто из-за malloc.
(Является ли этот пиковый рабочий набор размером надежный цифра?)