如何在没有竞争条件或错误共享的情况下使用OpenMP将此功能并行化?

时间:2018-11-15 14:10:23

标签: c performance parallel-processing openmp

我需要在没有种族条件或错误分享的情况下对一个功能进行仿效。我已经尝试了很多方法,但是还没有实现。功能是:

__inline static
void calculateClusterCentroIDs(int numCoords, int numObjs, int numClusters, float * dataSetMatrix, int * clusterAssignmentCurrent, float *clustersCentroID) {
    int * clusterMemberCount = (int *) calloc (numClusters,sizeof(float));

    // sum all points
    // for every point
    for (int i = 0; i < numObjs; ++i) {
        // which cluster is it in?
        int activeCluster = clusterAssignmentCurrent[i];

        // update count of members in that cluster
        ++clusterMemberCount[activeCluster];

        // sum point coordinates for finding centroid
        for (int j = 0; j < numCoords; ++j)
            clustersCentroID[activeCluster*numCoords + j] += dataSetMatrix[i*numCoords + j];
    }


    // now divide each coordinate sum by number of members to find mean/centroid
    // for each cluster
    for (int i = 0; i < numClusters; ++i) {
        if (clusterMemberCount[i] != 0)
            // for each coordinate
            for (int j = 0; j < numCoords; ++j)
                clustersCentroID[i*numCoords + j] /= clusterMemberCount[i];  /// XXXX will divide by zero here for any empty clusters!
    }

有什么想法我能实现吗?

谢谢。

3 个答案:

答案 0 :(得分:1)

这很简单

// sum all points
// for every point
for (int i = 0; i < numObjs; ++i) {
    // which cluster is it in?
    int activeCluster = clusterAssignmentCurrent[i];

    // update count of members in that cluster
    ++clusterMemberCount[activeCluster];

    // sum point coordinates for finding centroid
    #pragma omp parallel for
    for (int j = 0; j < numCoords; ++j)
        clustersCentroID[activeCluster*numCoords + j] += dataSetMatrix[i*numCoords + j];
}

内部循环非常适合并行化,因为所有写入都发生在clustersCentroID的不同元素上。您可以放心地假设默认计划不会出现明显的错误共享,它通常具有足够大的块。只是不要尝试使用schedule(static,1)之类的东西。

外部循环不那么容易并行化。您可以使用clusterMemberCountclusterMemberCount的缩减项,也可以执行以下操作:

#pragma omp parallel // note NO for
for (int i = 0; i < numObjs; ++i) {
    int activeCluster = clusterAssignmentCurrent[i];
    // ensure that exactly one thread works on each cluster
    if (activeCluster % omp_num_threads() != omp_get_thread_num()) continue;

仅当简单的解决方案不能产生足够的性能时才这样做。

另一个循环也很简单

#pragma omp parallel for
for (int i = 0; i < numClusters; ++i) {
    if (clusterMemberCount[i] != 0)
        // for each coordinate
        for (int j = 0; j < numCoords; ++j)
            clustersCentroID[i*numCoords + j] /= clusterMemberCount[i];
}

再次,无论是在正确性方面还是在错误共享方面,数据访问都是完全隔离的。

答案 1 :(得分:1)

您应该为numCoordsnumObjsnumClusters的期望值给出一个数量级,因为并行化的最佳方法取决于此。特别地,numCoords对于查看是否在坐标上对内部循环进行并行化/矢量化是否有意义很重要。例如,您要采用3D坐标还是1000尺寸?

另一种尝试是在第一个循环中使用if语句(不利于性能),静态调度(可能的负载不平衡)但每个线程都增加clusterMemberCount和{{1}的连续部分},从而限制了错误共享的风险。

clustersCentroID

答案 2 :(得分:-1)

添加至我的评论:++clusterMemberCount[activeCluster]形成直方图,并且当两个线程试图更新同一项目(或共享高速缓存行的相邻项目)时,这是有问题的。这需要作为一个顺序部分从循环中取出,或者必须通过为每个线程有一个单独的直方图副本然后进行组合来并行化。

您可以轻松地将这部分与第一个并行循环分开。

// Make the histogram
for (int i = 0; i < numObjs; ++i) {
    int activeCluster = clusterAssignmentCurrent[i];
    ++clusterMemberCount[activeCluster];
}

然后处理所有利用并行性的事情

// parallel processing
#pragma omp parallel for
for (int i = 0; i < numObjs; ++i) {
    int activeCluster = clusterAssignmentCurrent[i];
    for (int j = 0; j < numCoords; ++j)
        clustersCentroID[activeCluster*numCoords + j] += dataSetMatrix[i*numCoords + j];
}

第二次可能的错误共享是numCoords * sizeof(clustersCentroID[0]) % 64 != 0假设64字节缓存行。可以通过将clustersCentroID整体占用64字节的整数倍来缓解这种情况。

// Loop for numCoords, but index by numCoordsX
for (int j = 0; j < numCoords; ++j)
    clustersCentroID[activeCluster*numCoordsX + j] += dataSetMatrix[i*numCoords + j];