C++ 为什么这个搜索方法不可伸缩?

C++ 为什么这个搜索方法不可伸缩?,c++,parallel-processing,openmp,C++,Parallel Processing,Openmp,我想用openMP并行我的搜索算法,vTree是一个二叉搜索树,我想对每个点集应用我的搜索算法。下面是我的代码片段。两个点的搜索过程完全无关,因此可以是并行的。虽然它们确实需要读取同一棵树,但一旦构建,树就不会再被修改。因此它是只读的 然而,下面的代码显示了糟糕的可扩展性,在我的32核平台上,只实现了2倍的速度提升。是因为vTree被所有线程读取吗?如果是这样,我如何进一步优化代码 auto results = vector<vector<Point>>(part

我想用openMP并行我的搜索算法,
vTree
是一个二叉搜索树,我想对每个点集应用我的搜索算法。下面是我的代码片段。两个点的搜索过程完全无关,因此可以是并行的。虽然它们确实需要读取同一棵树,但一旦构建,树就不会再被修改。因此它是只读的

然而,下面的代码显示了糟糕的可扩展性,在我的32核平台上,只实现了2倍的速度提升。是因为
vTree
被所有线程读取吗?如果是这样,我如何进一步优化代码

    auto results = vector<vector<Point>>(particleNum);
    auto t3 = high_resolution_clock::now();
    double radius = 1.6;
#pragma omp parallel for
    for (decltype(points.size()) i = 0; i < points.size(); i++)
    {
        vTree.search(points[i], radius, results[i]);
    }
    auto t4 = high_resolution_clock::now();
    double searchTime = duration_cast<duration<double>>(t4 - t3).count();

搜索结果将被放入
结果中

我最好的猜测是您正在结果向量上缓存乒乓球。我假设您的“搜索”函数使用传入的结果向量作为放置点的位置,并且在整个算法中使用它来插入在搜索树中遇到的邻居。无论何时向该结果向量添加点,该向量对象的内部数据都将被修改。由于所有结果向量都打包在连续内存中,因此不同的结果向量很可能占用相同的缓存线。因此,当CPU保持缓存一致性时,它将不断锁定相关缓存线

解决这个问题的方法是使用一个内部的临时向量,在最后只给结果向量分配一次(如果使用移动语义,这可以很便宜地完成)。大概是这样的:

void VPTree::search(const Point& p, double radius, vector<Point>& result) const {
  vector<Point> tmp_result;
  // ... add results to "tmp_result"
  result = std::move(tmp_result);
  return;
}
void VPTree::搜索(常量点和点、双半径、向量和结果)常量{
向量tmp_结果;
//…将结果添加到“tmp_结果”
结果=标准::移动(tmp_结果);
返回;
}
或者,您也可以通过值(隐式使用移动)返回向量:

vector VPTree::搜索(常数点&p,双半径)常数{
矢量结果;
//…将结果添加到“结果”
返回结果;
}
欢迎来到移动语义的欢乐世界,以及它在解决这些类型的并发/缓存一致性问题方面是多么了不起


同样可以想象的是,您在从所有线程访问同一棵树时遇到了问题,但由于它都是只读操作,因此我非常确定,即使是在x86(以及其他Intel/AMD CPU)这样的保守体系结构上,这也不应该构成重大问题,但我可能是错的(可能是一种错误)“过度订阅”问题可能正在起作用,但它是可疑的)。其他问题可能包括OpenMP确实会产生相当大的开销(生成线程、同步等),这必须与您在这些并行循环中执行的实际操作的计算成本相比较(而且这并不总是一个有利的权衡)。此外,如果您的VPTree(我想象它代表“Vantage point Tree”)没有良好的引用位置(例如,您将其实现为一个链接树),那么无论您使用哪种方式,性能都将非常糟糕(正如我所解释的).

如果不使用探查器,我无法判断,但如果我猜到您的线程正在缓存彼此碰撞。@Mgetz,我认为在多核机器上,每个核都有自己的缓存,因此多线程代码应该能够使用更大的缓存,对吗?取决于平台。您可能有理由期望每个核都有自己的L1,但共享L3(除非您启用了超线程),并假设这些都是同一个包上的内核,并且…@Alaya:当写入发生时,所有缓存都需要在CPU之间保持同步(在一次近似情况下)。尝试为每个线程提供其自己的结果向量,并仅在完成后合并它们(如果您的算法可能的话).@Alaya在不知道系统配置细节的情况下,无法回答此问题。可能是因为您正在将线程繁殖到不同的NUMA节点上,而不是存储数据。在这种情况下,访问将非常痛苦。
result
的唯一操作类似于
Point*\u p=getP(),result.push_back(*\u p)
我重构了我的代码,并使用
std::vector
来存储节点,我得到了一些加速,但并不显著。您好,经过一些优化(通过将树展平为广度优先遍历数组),您似乎已经实现了
有利点树
,搜索过程可以实现线性加速,但是,在我的应用程序中,我需要处理移动点,因此每一轮,我都必须重新构建树,您对并行构建VP树有什么建议吗?
void VPTree::search(const Point& p, double radius, vector<Point>& result) const {
  vector<Point> tmp_result;
  // ... add results to "tmp_result"
  result = std::move(tmp_result);
  return;
}
vector<Point> VPTree::search(const Point& p, double radius) const {
  vector<Point> result;
  // ... add results to "result"
  return result;
}