如何使用 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并行化外循环以进行数组添加

C++ openmp并行程序在多核linux上如何最大化使用cpu

使用ray进行最近邻搜索的并行化

如何使用 OpenMP 在 GPU 上分配团队?

使用 OpenMP 并行化 C 中的基数排序