在编写fuse文件系统时,我有一个unordered_map<std::string, struct stat>
作为缓存,在启动时使用所有文件和目录启动,以减少对硬盘驱动器的读取。
为了满足readdir()
回调,我编写了以下循环:
const int sp = path == "/" ? 0 : path.size();
for (auto it = stat_cache.cbegin(); it != stat_cache.cend(); it++)
{
if (it->first.size() > sp)
{
int ls = it->first.find_last_of('/');
if (it->first.find(path, 0) == 0 && ls == sp)
filler(buf, it->first.substr(ls + 1).c_str(), const_cast<struct stat*>(&it->second), 0, FUSE_FILL_DIR_PLUS);
}
}
这个想法是一个路径以目录路径开头并且在目录路径末尾有最后一个斜杠的对象将是它的成员。我已经彻底测试了它并且它有效 插图:
Reading directory: /foo/bar
Candidate file: /bazboo/oof - not in dir (wrong prefix)
Candidate file: /foo/bar/baz/boo - not in dir (wrong lastslash location)
Candidate file: /foo/bar/baz - in dir!
然而,现在,这是非常缓慢的(特别是在缓存中有超过五十万个对象的文件系统中)。 Valgrind / Callgrind特别指责std::string:find_last_of()
和std::string::find()
来电。
我已经添加了if (it->first.size() > sp)
以尝试加速循环,但性能提升最多只是最小化。
我还尝试通过将循环并行化为四个块来加速此例程,但在unordered_map::cbegin()
期间以段错误结束。
我不再拥有实际的代码,但我相信它看起来像这样:
const int sp = path == "/" ? 0 : path.size();
ThreadPool<4> tpool;
ulong cq = stat_cache.size()/4;
for (int i = 0; i < 4; i++)
{
tpool.addTask([&] () {
auto it = stat_cache.cbegin();
std::next(it, i * cq);
for (int j = 0; j < cq && it != stat_cache.cend(); j++, it++)
{
if (it->first.size() > sp)
{
int ls = it->first.find_last_of('/');
if (it->first.find(path, 0) == 0 && ls == sp)
filler(buf, it->first.substr(ls + 1).c_str(), const_cast<struct stat*>(&it->second), 0, FUSE_FILL_DIR_PLUS);
}
}
});
}
tpool.joinAll();
我也尝试过将它拆分为地图存储桶,unordered_map::cbegin(int)
为它提供了方便的重载,但它仍然是分离出来的。
同样,我目前正在使用第一个(非并行)代码并希望获得该代码的帮助,因为并行化的代码不起作用。我只是觉得我会将我的并行化尝试包括在内,完成性,禁忌和努力证明。
是否还有其他选项可用于优化此循环?
答案 0 :(得分:2)
这里要做的很简单的事情就是改变if
:
if (it->first.find(path, 0) == 0 && ls == sp)
简单地说:
if (ls == sp && it->first.find(path, 0) == 0)
显然,比较两个整数比查找子串要快得多。
我不能保证它会改变性能,但这可能有助于跳过大量不必要的std::string::find
次呼叫,这是一件微不足道的事情。也许编译器已经这样做了,我会查看反汇编。
此外,由于filepathes无论如何都是唯一的,我会使用std::vector<std::pair<...>>
代替 - 更好的缓存局部性,更少的内存分配等等。只需记住首先保留大小。
答案 1 :(得分:1)
真正的问题是
null
有效地消除无序_maps的最大优势并暴露其中一个弱点。你不仅没有O(1)查找,而且你可能不得不在地图中搜索以找到一个条目,这使得rutine O(N)具有非常大的K(如果不是额外的N即O(N) ^ 2))。
最快的解决方案是O(1)用于查找(在幸运的无序映射中),O(strlen(目标))在桶方案中或O(lgN)在二进制中。然后沿for (auto it = stat_cache.cbegin(); it != stat_cache.cend(); it++)
列出子项列表,为O(#children)。