Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/155.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在大内存映射文件中搜索_C++_Algorithm_Search_Optimization_Io - Fatal编程技术网

C++ 在大内存映射文件中搜索

C++ 在大内存映射文件中搜索,c++,algorithm,search,optimization,io,C++,Algorithm,Search,Optimization,Io,我有一个大的数据结构存储在内存映射文件中。数据结构非常简单: struct Header { ...some metadata... uint32_t index_size; uint64_t index[] }; 这个头被放在文件的开头,它使用了一个结构hack-可变大小的结构,最后一个元素的大小不是固定的,可以更改 char* mmaped_region = ...; // This memory comes from memory mapped file! He

我有一个大的数据结构存储在内存映射文件中。数据结构非常简单:

struct Header {
    ...some metadata...
    uint32_t index_size;
    uint64_t index[]
};
这个头被放在文件的开头,它使用了一个结构hack-可变大小的结构,最后一个元素的大小不是固定的,可以更改

char* mmaped_region = ...;  // This memory comes from memory mapped file!
Header* pheader = reinterpret_cast<Header*>(mmaped_region);
所有数据元素都已排序(小于为数据元素定义的关系)。它们存储在与标头相同的内存映射区域中,但从结尾到开头。无法移动数据元素,因为它们的大小是可变的,而不是可变的-头中的索引在排序过程中移动。这很像现代数据库中的B树页面,索引数组通常称为间接向量

搜索

该数据结构使用插值搜索算法(步骤有限)进行搜索,而不是使用二进制搜索。首先,我要搜索一个完整的
索引
数组,我试图计算-如果分布均匀,搜索的元素可以存储在哪里。我得到一些计算过的索引——看看这个索引中的元素,它通常不匹配。然后我缩小搜索范围并重复。插值搜索步骤的数量受到一些小数字的限制。然后用二进制搜索数据结构。这对于小数据集非常有效,因为分布通常是均匀的。插值搜索只需几次迭代,我们就完成了

问题定义。 内存映射区域实际上可能非常大。为了进行测试,我使用32Gb的文件备份存储并搜索一些随机密钥。这是非常缓慢的,因为这种模式会导致大量随机磁盘读取(所有数据都不能缓存在内存中)


这里可以做什么?我认为使用
madvise
syscall设置MADV_RANDOM会有所帮助,但可能不会太多。我想与B-树搜索速度保持一致。也许可以使用
mincore
syscall检查哪些数据元素在插值搜索过程中可以轻松检查?也许我可以使用某种类型的预取?

这里的插值搜索似乎是个好主意。它通常有一个小的好处,但在这种情况下,即使保存少量的迭代也会有很大帮助,因为它们很慢(磁盘I/O)

但是,实际数据库在其索引中复制实际键值。这方面的空间开销在性能改进中是完全合理的。btree是一种进一步的改进,因为它们将多个相关节点打包在一个连续的内存块中,从而进一步减少了磁盘搜索

这可能也是您的正确解决方案。您应该复制密钥以避免磁盘I/O。如果无法更改现有的头,您可能可以通过在单独的结构中复制密钥并将其完全保留在内存中来避免

一种折衷方案是可能的,您只需缓存前N级二进制搜索的顶部(2^N)-1个键。这意味着你必须放弃你的插值搜索的这一部分,但正如前面提到的插值不是一个巨大的胜利无论如何。节省下来的磁盘将很容易获得回报。即使只缓存中位数键(N=1),每次查找也会为您节省一次磁盘搜索。一旦缓存用完,仍然可以使用插值


相比之下,任何试图篡改内存映射参数的尝试最多只能使您的速度提高几%。“与B树相称”是<强>不>强>将发生。如果你的算法需要那些物理搜索,你就输了。没有魔法精灵之尘会修复坏的算法或坏的数据结构。

+1表示“没有魔法精灵之尘会修复坏的算法或坏的数据结构”。我需要非常快速的追加操作,所以我使用此数据结构。这非常类似于数据库中的预写登录。使用b-tree,我们需要复制内容、分割节点等等。有了这个数据结构,我可以每秒写入数百万个条目。你关于魔法精灵尘埃的评论是绝对正确的,但是在b-树中搜索需要log_m(N)磁盘搜索和插值搜索需要log(log(N)),并且取决于m-插值可以更快。事实上,我预计插值+二进制搜索会慢很多,但我知道我在做什么:)与B树相称,可以慢10倍,这是可以的。100倍或更多是不行的,我相信用这种算法是可以做到的。@Lazin:如果你有32GB的对象,你可能有十亿或更多的对象,但可能至少有一百万个。因此,您将看到20或30次搜索,而带有值的缓存索引将允许您直接(在一百万个对象的情况下,内存中的所有键)或几乎直接(在十亿个对象的情况下,缓存1600万个键,对剩余128个对象进行插值,以便进行4次搜索)@Lazin:如果您需要这么快的附加,存储树加上一个“最近更新缓存”,我当然相信这感觉很慢。我不得不解决一个类似的问题,但速度非常慢,因为它使用了CD上的数据库。硬盘搜索时间约为10毫秒,CD搜索时间约为150毫秒。仅需20次搜索(100万项),每次查询就需要3秒钟。包含实际键的索引只需6MB(压缩效果非常好),而CD驱动器可以在大约1秒内读取该索引。
MADV_RANDOM
。它所做的只是完全禁用readahead,这意味着除了在随机位置上获得硬页面错误外,在前几百KB的点击中也会获得硬页面错误。唯一有帮助的两件事是(a)拥有比文件大小更多的物理RAM和(b)改进引用的位置。@Damon:从技术上讲,内存中只有键就足够了,检索正确记录的单文件访问不会让你丧命。@mAlters:是的,尽管即使是几次单次查找也仍然很痛苦不幸的是,我手头没有一个好的解决方案(显然)。如果dat
uint64_t offset = pheader->index[x];
DataItem* item = reinterpret_cast<DataItem*>(mmaped_region + offset);
// At this point, variable item contains pointer to data element
// if variable x contains correct index value (less than pheader->index_size)