如何使用 OpenMP 并行化最近邻搜索
Posted
技术标签:
【中文标题】如何使用 OpenMP 并行化最近邻搜索【英文标题】:How to parallelize nearest neighbour search using OpenMP 【发布时间】:2020-01-31 19:50:53 【问题描述】:基本上,我有一个集合 std::vector<std::pair<std::vector<float>, unsigned int>>
,其中包含大小为 512 (2048 bytes
) 的模板对 std::vector<float>
及其对应的标识符 unsigned int
。
我正在编写一个函数,其中为我提供了一个模板,我需要返回集合中最相似模板的标识符。我正在使用点积来计算相似度。
我的幼稚实现如下所示:
// Should return false if no match is found (ie. similarity is 0 for all templates in collection)
bool identify(const float* data, unsigned int length, unsigned int& label, float& similarity)
bool found = false;
similarity = 0.f;
for (size_t i = 0; i < collection.size(); ++i)
const float* candidateTemplate = collection[i].first.data();
float consinSimilarity = getSimilarity(data, candidateTemplate, length); // computes cosin sim between two vectors, implementation depends on architecture.
if (consinSimilarity > similarity)
found = true;
similarity = consinSimilarity;
label = collection[i].second;
return found;
如何使用并行化来加快速度。我的收藏可能包含数百万个模板。我读到您可以添加#pragma omp parallel for reduction
,但我不完全确定如何使用它(如果这甚至是最好的选择)。
另请注意: 对于我的点产品实现,如果基础架构支持 AVX 和 FMA,我将使用 this 实现。 由于只有有限数量的 SIMD 寄存器,这会在我们并行化时影响性能吗?
【问题讨论】:
【参考方案1】:由于我们无法访问实际编译的示例(这本来很好),我实际上并没有尝试编译下面的示例。尽管如此,除了一些小的错别字(也许)之外,总体思路应该很清楚。
任务是找到相似度的最大值和对应的标签,为此我们确实可以使用reduction
,但是由于我们需要找到一个值的最大值然后存储对应的标签,所以我们使用一对同时存储两个值,以便在 OpenMP 中将其实现为reduction
。
我稍微改写了您的代码,使用变量的原始命名 (temp
) 可能会使事情变得更难阅读。基本上,我们并行执行搜索,因此每个线程找到一个最佳值,然后我们要求 OpenMP 找到线程之间的最佳解决方案 (reduction
),我们就完成了。
//Reduce by finding the maximum and also storing the corresponding label, this is why we use a std::pair.
void reduce_custom (std::pair<float, unsigned int>& output, std::pair<float, unsigned int>& input)
if (input.first > output.first) output = input;
//Declare an OpenMP reduction with our pair and our custom reduction function.
#pragma omp declare reduction(custom_reduction : \
std::pair<float, unsigned int>: \
reduce_custom(omp_out, omp_in)) \
initializer(omp_priv(omp_orig))
bool identify(const float* data, unsigned int length, unsigned int& label, float& similarity)
std::pair<float, unsigned int> temp(0.0, label); //Stores thread local similarity and corresponding best label.
#pragma omp parallel for reduction(custom_reduction:temp)
for (size_t i = 0; i < collection.size(); ++i)
const float* candidateTemplate = collection[i].first.data();
float consinSimilarity = getSimilarity(data, candidateTemplate, length);
if (consinSimilarity > temp.first)
temp.first = consinSimilarity;
temp.second = collection[i].second;
if (temp.first > 0.f)
similarity = temp.first;
label = temp.second;
return true;
return false;
关于您对 SIMD 寄存器数量有限的担忧,它们的数量取决于您使用的特定 CPU。据我所知,每个内核都有一定数量的可用向量寄存器,所以只要你使用的数量不超过可用的数量,现在也应该没问题,此外,AVX512 例如提供 32 个向量寄存器和 2 个每个核心的向量运算的算术单元,因此计算资源的用完并非易事,由于内存局部性差(特别是在您的情况下,向量被保存在所有地方),您更有可能遭受痛苦。我当然可能是错的,如果是这样,请随时在 cmets 中纠正我。
【讨论】:
感谢 Qubit 的回答,这看起来很棒。你也是正确的,它应该是一个指针const float* candidateTemplate
。我已经编辑了我的问题以反映这一变化。
也许您可以再回答一个问题。 #pragma omp parallel
指令将启动多少个线程?是否依赖于系统?
通常,它会启动与该系统上可用的逻辑处理器一样多的线程(即,不管它们是否被占用,它都会启动尽可能多的线程)。您会发现在某些应用程序中运行较少的线程可能是有益的(即超线程并不总是您的朋友) - 或者您可能只想将部分资源用于任务。您可以通过在 omp pragma 指令中添加 num_threads(some_value_here)
来控制线程数。您还应该考虑添加schedule(static)
,我认为这对您来说最有意义。
我是否需要使用 Private 属性以便每个线程都有自己的 temp
变量副本?前任。 #pragma omp parallel for reduction(custom_reduction:temp) private(temp)
不,OpenMP 内部会创建一个线程私有变量并最终将它们合并,这两者都将根据您指定归约子句的方式来完成。以上是关于如何使用 OpenMP 并行化最近邻搜索的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 OpenMP 通过 C++ std::list 并行化 for 循环?
如何使用带有串行内循环的openMP并行化外循环以进行数组添加