这个问题经常在StackOverflow上重复出现,但我已经阅读了所有以前的相关答案,并对问题略有不同。
我有一个包含4.75亿行相同大小的23Gb文件,每行包含一个40个字符的哈希码,后跟一个标识符(整数)。
我有一个传入的哈希码流 - 总共有数十亿个哈希码 - 对于每个传入的哈希码,我需要找到它并打印出相应的标识符。这项工作虽然很大,但只需要完成一次。
文件太大,我无法读入内存,因此我一直尝试以下列方式使用map:
codes = (char *) mmap(0,statbuf.st_size,PROT_READ,MAP_SHARED,codefile,0);
然后我只使用基于代码中地址的地址算法进行二进制搜索。
这似乎开始正常工作并在几秒内产生几百万个标识符,使用100%的cpu,但是经过一些看似随机的时间,它减慢到a爬行。当我使用ps查看进程时,它已从状态" R"使用100%的cpu,状态" D" (磁盘绑定)使用1%的cpu。
这是不可重复的 - 我可以在相同的数据上再次启动该过程,它可能会在#34;慢速爬行之前运行5秒或10秒。发生。昨晚一次,在发生这种情况之前我差不多花了一分钟。
一切都是只读的,我没有尝试对文件进行任何写入,而且我已经停止了机器上的所有其他进程(我控制)。它是一台现代的Red Hat Enterprise Linux 64位计算机。
有谁知道为什么这个过程会受到磁盘限制以及如何阻止它?
更新:
感谢大家的回答和感谢;之前我没有尝试过所有各种改进,因为我想知道我是否以某种方式错误地使用了mmap。但答案的要点似乎是,除非我能将所有东西都挤进记忆中,否则我将不可避免地遇到问题。所以我将哈希码的大小压缩到没有创建任何重复项的前导前缀的大小 - 前15个字符就足够了。然后我将生成的文件拉入内存,并分别运行大约20亿个传入的哈希码。
答案 0 :(得分:3)
要做的第一件事是拆分文件。
使用哈希码创建一个文件,使用整数ID创建另一个文件。由于行是相同的,因此在找到结果后它将排列正常。您还可以尝试将每个第n个哈希放入另一个文件然后存储索引的方法。
例如,每个第1000个散列键都放入带索引的新文件中,然后将其加载到内存中。然后二进制扫描而不是。这将告诉您需要在文件中进一步扫描的1000个条目的范围。是的,这样做会好的!但可能远不止于此。就像大概每20个记录一样,将文件大小减少20 + - 如果我想的那么好。
换句话说,扫描后你只需触摸磁盘上几千字节的文件即可。
另一个选择是拆分文件并将其放在多台计算机的内存中。然后只需二进制扫描每个文件。这将产生绝对最快的搜索,零磁盘访问...
答案 1 :(得分:2)
您是否考虑过攻击PATRICIA trie算法?在我看来,如果你可以构建数据文件的PATRICIA树表示,它指的是散列和整数值的文件,那么你可以将每个项目减少到节点指针(2 * 64位?),位测试偏移(本场景中为1个字节)和文件偏移量(uint64_t,可能需要对应多个fseek()s)。
答案 2 :(得分:2)
有谁知道为什么这个过程会受到磁盘限制以及如何阻止它?
二进制搜索需要在文件中进行大量搜索。在整个文件不适合内存的情况下,页面缓存不能很好地处理大搜索,导致您看到的行为。
解决这个问题的最佳方法是减少/防止重大搜索并使页面缓存为您工作。
有三个想法:
如果您可以对输入流进行排序,则可以使用类似以下算法的方式搜索文件:
code_block <- mmap the first N entries of the file, where N entries fit in memory
max_code <- code_block[N - 1]
while(input codes remain) {
input_code <- next input code
while(input_code > max_code) {
code_block <- mmap the next N entries of the file
max_code <- code_block[N - 1]
}
binary search for input code in code_block
}
如果无法对输入流进行排序,则可以通过构建内存中的数据索引来减少磁盘搜索。传递大文件,然后创建一个table
:
record_hash, offset into file where this record starts
不要在此表中存储所有记录 - 仅存储每个第K条记录。选择一个大K,但小到足以让它适合记忆。
要在大文件中搜索给定的目标哈希,请在内存表中执行二进制搜索,以查找table
中小于目标哈希的最大哈希值。说这是table[h]
。然后,mmap从table[h].offset
开始到table[h+1].offset
结束的段,并进行最终的二进制搜索。这将大大减少磁盘搜索次数。
如果这还不够,您可以拥有多层索引:
record_hash, offset into index where the next index starts
当然,您需要提前知道有多少层索引。
最后,如果您有额外的资金可用,您总是可以购买超过23 GB的RAM,并再次使这成为一个内存限制问题(我只看了戴尔的网站 - 你拿起一个新的具有32 GB RAM的低端工作站,售价不到1,400澳元。当然,从磁盘读取大量数据需要一段时间,但一旦它存在,你就会被设置。
答案 3 :(得分:1)
不要使用mmap
,而应考虑使用普通的lseek
+ read
。您可以定义一些辅助函数来读取散列值或其对应的整数:
void read_hash(int line, char *hashbuf) {
lseek64(fd, ((uint64_t)line) * line_len, SEEK_SET);
read(fd, hashbuf, 40);
}
int read_int(int line) {
lseek64(fd, ((uint64_t)line) * line_len + 40, SEEK_SET);
int ret;
read(fd, &ret, sizeof(int));
return ret;
}
然后像往常一样进行二分查找。它可能会慢一点,但它不会开始咀嚼虚拟内存。
答案 4 :(得分:1)
我们不知道背后的故事。所以很难给你明确的建议。你有多少记忆?你的硬盘有多复杂?这是一个学习项目吗?谁在为你付出时间? 32GB的ram看起来并不那么昂贵,相比之下,两天的工作时间为50美元/小时。这需要多快才能运行?在盒子外面你愿意走多远?您的解决方案是否需要使用高级OS概念?你和C的节目结婚了吗?让Postgres处理这个怎么样?
这是一种低风险的替代方案。此选项不像其他建议那样具有智力吸引力,但有可能为您带来显着收益。将文件分成3块8GB或6块4GB(取决于你周围的机器,它需要舒适地适合内存)。在每台机器上运行相同的软件,但在内存中并在每个机器周围放置一个RPC存根。将RPC调用者写入3或6个工作程序中的每一个,以确定与给定哈希代码关联的整数。