- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我已经声明并定义了以下 HashTable 类。请注意,我需要一个哈希表的哈希表,因此我的 HashEntry 结构包含一个 HashTable 指针。公共(public)部分没什么大不了的,它具有传统的哈希表函数,因此为简单起见,我删除了它们。
enum Status{ACTIVE, DELETED, EMPTY};
enum Type{DNS_ENTRY, URL_ENTRY};
class HashTable{
private:
struct HashEntry{
std::string key;
Status current_status;
std::string ip;
int access_count;
Type entry_type;
HashTable *table;
HashEntry(
const std::string &k = std::string(),
Status s = EMPTY,
const std::string &u = std::string(),
const int &a = int(),
Type e = DNS_ENTRY,
HashTable *t = NULL
): key(k), current_status(s), ip(u), access_count(a), entry_type(e), table(t){}
};
std::vector<HashEntry> array;
int currentSize;
public:
HashTable(int size = 1181, int csz = 0): array(size), currentSize(csz){}
};
我正在使用二次探测,当我点击 array.size()/2
时,我在重新哈希函数中将 vector 的大小加倍。当需要更大的表大小时使用以下列表。
int a[16] = {49663, 99907, 181031, 360461,...}
我的问题是这个类消耗了太多内存。我刚刚用 massif 分析了它,发现它需要 33MB(3300 万字节!)的内存来进行 125000 次插入。说清楚,实际上
1 insertion -> 47352 Bytes
8 insertion -> 48376 Bytes
512 insertion -> 76.27KB
1000 insertion 2MB (array size increased to 49663 here)
27000 insertion-> 8MB (array size increased to 99907 here)
64000 insertion -> 16MB (array size increased to 181031 here)
125000 insertion-> 33MB (array size increased to 360461 here)
这些可能是不必要的,但我只是想向您展示内存使用如何随着输入而变化。如您所见,重新散列完成后,内存使用量翻倍。例如,我们的初始数组大小为 1181。我们刚刚看到 125000 个元素 -> 33MB。
为了调试问题,我将初始大小更改为 360461。现在 127000 插入不需要重新散列。我看到这个初始值使用了 20MB 的内存。这仍然很大,但我认为这表明重新散列存在问题。以下是我的 rehash 函数。
void HashTable::rehash(){
std::vector<HashEntry> oldArray = array;
array.resize(nextprime(array.size()));
for(int j = 0; j < array.size(); j++){
array[j].current_status = EMPTY;
}
for(int i = 0; i < oldArray.size(); i++){
if(oldArray[i].current_status == ACTIVE){
insert(oldArray[i].key);
int pos = findPos(oldArray[i].key);
array[pos] = oldArray[i];
}
}
}
int nextprime(int arraysize){
int a[16] = {49663, 99907, 181031, 360461, 720703, 1400863, 2800519, 5600533, 11200031, 22000787, 44000027};
int i = 0;
while(arraysize >= a[i]){i++;}
return a[i];
}
这是在重新散列和其他地方使用的插入函数。
bool HashTable::insert(const std::string &k){
int currentPos = findPos(k);
if(isActive(currentPos)){
return false;
}
array[currentPos] = HashEntry(k, ACTIVE);
if(++currentSize > array.size() / 2){
rehash();
}
return true;
}
我在这里做错了什么?即使它是由重新散列引起的,当没有进行重新散列时它仍然是 20MB,我相信 20MB 对于 100k 项目来说太多了。这个哈希表应该包含大约 800 万个元素。
最佳答案
360,461 个 HashEntry 占用 20 MB 的空间这一事实不足为奇。您是否尝试查看 sizeof(HashEntry)
?
每个 HashEntry 包括两个 std::strings、一个指针和三个 int。正如老笑话所说,要回答“一个字符串有多长?”这个问题并不容易,在这种情况下,因为有各种各样的字符串实现和优化,所以您可能会发现 sizeof(std: :string)
是 4 到 32 字节之间的任何位置。 (在 32 位架构上它只有 4 个字节。)实际上,一个字符串需要三个指针和字符串本身,除非它恰好为空。如果 sizeof(std::string) 与 sizeof(void*) 相同,那么您可能有一个不太新的 GNU 标准库,其中 std::string
是一个指向包含两个指针、一个引用计数和字符串本身的 block 的不透明指针。如果 sizeof(std::string) 是 32 字节,那么您可能有一个最近的 GNU 标准库实现,其中在字符串结构中有一些额外的空间用于短字符串优化。查看 Why does libc++'s implementation of std::string take up 3x memory as libstdc++? 的答案进行一些测量。假设每个字符串 32 个字节,忽略细节;它不会关闭太多。
所以两个字符串(每个 32 个字节)加上一个指针(8 个字节)加上三个 int(另外 12 个字节)和四个填充字节,因为其中一个 int 在两个 8 字节对齐的对象之间,总共是每个 HashEntry 88 个字节。如果您有 360,461 个哈希条目,那将是 31,720,568 字节,大约 30 MB。您“仅”使用 20MB 的事实可能是因为您使用的是旧的 GNU 标准库,它将空字符串优化为单个指针,并且您的大部分字符串都是空字符串,因为一半的插槽从未使用过.
现在,让我们看一下重新哈希。精简到最基本的:
void rehash() {
std::vector<HashEntry> tmp = array; /* Copy the entire array */
array.resize(new_size()); /* Internally does another copy */
for (auto const& entry : tmp)
if (entry.used()) array.insert(entry); /* Yet another copy */
}
在高峰期,我们有两个较小数组的拷贝以及新的大数组。即使新阵列只有 20 MB,峰值内存使用量几乎是它的两倍也就不足为奇了。 (事实上 ,这再次出人意料地小,而不是出奇地大。可能实际上不需要更改新 vector 的地址,因为它位于当前分配的内存空间的末尾,可以扩展。)
请注意,我们为所有这些数据做了两份拷贝,array.resize()
可能做了另一份。让我们看看是否可以做得更好:
void rehash() {
std::vector<HashEntry> tmp(new_size()); /* Make an array of default objects */
for (auto const& entry: array)
if (entry.used()) tmp.insert(entry); /* Copy into the new array */
std::swap(tmp, array); /* Not a copy, just swap three pointers */
}
这样,我们只做一份。我们不是通过调整大小进行(可能的)内部复制,而是对新元素进行批量构造,这应该是相似的。 (它只是将内存归零。)
此外,在新版本中,我们每个只复制一次实际字符串,而不是每个复制两次,这是复制中最繁琐的部分,因此可能会节省相当多的钱。
适当的字符串管理可以进一步减少开销。 rehash 实际上不需要复制字符串,因为它们没有改变。所以我们可以将字符串保存在别处,比如在字符串 vector 中,并且只需在 HashEntry 中使用 vector 中的索引。由于您不希望保存数十亿个字符串,只有数百万个,因此索引可以是一个四字节的 int。通过还混洗 HashEntry 字段并将枚举减少到一个字节而不是四个字节(在 C++11 中,您可以指定枚举的基础整数类型),HashEntry 可以减少到 24 个字节,并且不会不需要为尽可能多的字符串描述符留出空间。
关于c++ - 哈希表需要大量内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34561147/
我有一台 MySQL 服务器和一台 PostgreSQL 服务器。 需要从多个表中复制或重新插入一组数据 MySQL 流式传输/同步到 PostgreSQL 表。 这种复制可以基于时间(Sync)或事
如果两个表的 id 彼此相等,我尝试从一个表中获取数据。这是我使用的代码: SELECT id_to , email_to , name_to , status_to
我有一个 Excel 工作表。顶行对应于列名称,而连续的行每行代表一个条目。 如何将此 Excel 工作表转换为 SQL 表? 我使用的是 SQL Server 2005。 最佳答案 这取决于您使用哪
我想合并两个 Django 模型并创建一个模型。让我们假设我有第一个表表 A,其中包含一些列和数据。 Table A -------------- col1 col2 col3 col
我有两个表:table1,table2,如下所示 table1: id name 1 tamil 2 english 3 maths 4 science table2: p
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 1 年前。 Improve th
下面两个语句有什么区别? newTable = orginalTable 或 newTable.data(originalTable) 我怀疑 .data() 方法具有性能优势,因为它在标准 AX 中
我有一个表,我没有在其中显式定义主键,它并不是真正需要的功能......但是一位同事建议我添加一个列作为唯一主键以随着数据库的增长提高性能...... 谁能解释一下这是如何提高性能的? 没有使用索引(
如何将表“产品”中的产品记录与其不同表“图像”中的图像相关联? 我正在对产品 ID 使用自动增量。 我觉得不可能进行关联,因为产品 ID 是自动递增的,因此在插入期间不可用! 如何插入新产品,获取产品
我有一个 sql 表,其中包含关键字和出现次数,如下所示(尽管出现次数并不重要): ____________ dog | 3 | ____________ rat | 7 | ____
是否可以使用目标表中的LAST_INSERT_ID更新源表? INSERT INTO `target` SELECT `a`, `b` FROM `source` 目标表有一个自动增量键id,我想将其
我正在重建一个搜索查询,因为它在“我看到的”中变得多余,我想知道什么 (albums_artists, artists) ( ) does in join? is it for boosting pe
以下是我使用 mysqldump 备份数据库的开关: /usr/bin/mysqldump -u **** --password=**** --single-transaction --databas
我试图获取 MySQL 表中的所有行并将它们放入 HTML 表中: Exam ID Status Assigned Examiner
如何查询名为 photos 的表中的所有记录,并知道当前用户使用单个查询将哪些结果照片添加为书签? 这是我的表格: -- -- Table structure for table `photos` -
我的网站都在 InnoDB 表上运行,目前为止运行良好。现在我想知道在我的网站上实时发生了什么,所以我将每个页面浏览量(页面、引荐来源网址、IP、主机名等)存储在 InnoDB 表中。每秒大约有 10
我在想我会为 mysql 准备两个表。一个用于存储登录信息,另一个用于存储送货地址。这是传统方式还是所有内容都存储在一张表中? 对于两个表...有没有办法自动将表 A 的列复制到表 B,以便我可以引用
我不是程序员,我从这个表格中阅读了很多关于如何解决我的问题的内容,但我的搜索效果不好 我有两张 table 表 1:成员 id*| name | surname -------------------
我知道如何在 ASP.NET 中显示真实表,例如 public ActionResult Index() { var s = db.StaffInfoDBSet.ToList(); r
我正在尝试运行以下查询: "insert into visits set source = 'http://google.com' and country = 'en' and ref = '1234
我是一名优秀的程序员,十分优秀!