【问题标题】:Intel TBB parallel_reduce returning incorrect resultIntel TBB parallel_reduce 返回不正确的结果
【发布时间】:2015-04-23 04:33:32
【问题描述】:

我正在尝试使用 Intel TBB parallel_reduce 来计算对 Connect 4 游戏中用户移动的最佳响应。我写了一个简单的函数,当传递一个列号时,它会返回一个与玩该动作的效用相对应的启发式分数。我的意图是并行调用这个函数,然后返回返回结果的缩减。减少将是启发式得分最高的移动。

但是我发现我得到的结果不一致。我希望从并行减少中返回第 0 列的移动(因为我已经编写了启发式得分函数,使其返回 7 - 列号作为得分)。

我已附上 C++ 代码。这是它产生的示例输出:

返回移动0

返回移动 1

返回移动 2

返回移动 6

返回移动 4

返回移动 5

比较动作5返回动作3和6

比较第 3 步和第 4 步

比较第 3 步和第 5 步

比较第 2 步和第 3 步

最佳步数是 2

我已经多次查看代码,无法理解为什么它并不总是返回 0 作为最佳移动。它有时会返回零,但并非总是如此。正如您在上面看到的,移动 0 被评估,但归约 lambda 函数永远不会将其作为输入。任何帮助将不胜感激。我有一个模糊的怀疑,我使用的 0 的身份,可能不对。但是我不确定它应该是什么。

#include <tbb\blocked_range.h>
#include <tbb\parallel_reduce.h>

#include <iostream>
#include <vector>

int getMoveHueristicScore(int columnCounter) {

    int returnValue = 7 - columnCounter;
    return returnValue;
}

int bestHeuristicScore(int numberOfColumns) {

    std::vector<int> moveScores(numberOfColumns, -1 * INT_MAX);

    return tbb::parallel_reduce(
        tbb::blocked_range<int>(0, numberOfColumns),
        0,
        [=, &moveScores](const tbb::blocked_range<int>& range, int bestMove)->int {
        int bestScore = -1 * INT_MAX;
        for (int columnCounter = range.begin(); columnCounter != range.end(); ++columnCounter) {
            moveScores.at(columnCounter) = getMoveHueristicScore(columnCounter);
            if (moveScores.at(columnCounter) > bestScore) {
                bestScore = moveScores.at(columnCounter);
                bestMove = columnCounter;
            }
        }
        std::cout << "Return move " << bestMove << std::endl;
        return bestMove;
    },
        [=, &moveScores](int bestMove1, int bestMove2)->int {
        std::cout << "Compare moves " << bestMove1 << " & " << bestMove2 << std::endl;
        if (moveScores.at(bestMove1) > moveScores.at(bestMove2)) {
            return bestMove1;
        }
        else {
            return bestMove2;
        }
    }
    );
}

int main() {

    int bestMove = bestHeuristicScore(7);
    std::cout << "Best move is " << bestMove << std::endl;
    return 0;
}

【问题讨论】:

    标签: c++ parallel-processing intel tbb


    【解决方案1】:

    在这种情况下tbb::parallel_reduce 没有问题。问题是您的代码忽略了init 函数的func 参数。

    TBB 对如何执行缩减有不同的选择。它可以选择在不同线程中为不同的子范围调用func,然后使用reduction函数组合结果;它可以在同一个线程中多次调用func,每次都传入上一次调用的结果;或者它可以使用两种策略的组合。

    看看你对func的实现:

    [=, &moveScores](const tbb::blocked_range<int>& range, int bestMove)->int {
        int bestScore = -1 * INT_MAX;
        for (int columnCounter = range.begin(); columnCounter != range.end(); ++columnCounter) {
            moveScores.at(columnCounter) = getMoveHueristicScore(columnCounter);
            if (moveScores.at(columnCounter) > bestScore) {
                bestScore = moveScores.at(columnCounter);
                bestMove = columnCounter;
            }
        }
        std::cout << "Return move " << bestMove << std::endl;
        return bestMove;
    }
    

    从未使用过init 参数bestMove;相反,值bestScore 被初始化为某个虚拟值。 TBB 可能会多次调用func,每次都传入上一次调用计算的bestMove,但这会被忽略,因此最终结果将是来自任何子范围的bestMove,恰好传递到最后一次调用。

    修复方法是使用bestMove来初始化最好成绩,如下:

    int bestScore = moveScores.at(bestMove);
    

    然后TBB会将运行最好的分数从func的一次调用带到下一次,确保最终结果确实是全球最好的分数。

    【讨论】:

    • 还没来得及检查。这周会检查。已经被其他事情缠住了。
    • 我试过了,但它崩溃了。仔细看,我不确定这个解释是否对我有意义。建议的更改是并行减少的 Map 部分。我的印象是,所有映射部分的输出都提供给了 reduce 函数(第二个 lambda),而不是另一个运行映射部分的线程。也许我对 TBB 的理解是错误的。我不知道现在该怎么办。
    • func 并不是真正的地图,它是从initial_value 开始的子范围的缩减。第二个函数reduction 可用于连接不同工作人员计算的子范围的结果。 func 可用于累积由单个工作人员计算的多个子范围,因此以提供的初始值而不是标识值开头很重要。 software.intel.com/en-us/node/506063
    【解决方案2】:

    您声称减少的左侧标识是0,显然不是这样。你必须提供这样的身份,并在你的 reducer 中处理它。尝试-1 并明确处理它。

    -INT_MAX 减 1。

    并在遇到错误时明确捕获(删除=)。了解捕获的内容有时会很有帮助。

    我很惊讶您的示例输出在减少时缺少1 的提及,但我对缺少0 并不感到惊讶。

    【讨论】:

    • 谢谢@Yakk。我通过用 -1 填充向量来纠正 -1 * INT_MAX 的问题。我将标识更改为 -1 并更新减速器以在任何参数为 -1 时返回另一个参数。我还删除了“=”以明确捕获。但它没有帮助。我仍然得到不一致的结果。返回移动 0 返回移动 1 返回移动 2 返回移动 6 返回移动 4 返回移动 3 比较移动 3 和 4 返回移动 5 比较移动 5 和 6 比较移动 3 和 5 比较移动 2 和 3 最佳移动是 2
    • @AggieMan 所以,我用谷歌搜索了 tbb parallel_reduce 错误,并在几年前遇到了一些令人不安的错误(挂起、无法清理等)。也许代码是......只是错误?有两个重载,另一个看起来较旧(因为它早于 lambda),可能尝试使用较旧的重载可能会减少错误?
    • 谢谢@Yakk。那真不幸。也许英特尔很快就会解决这个问题。我有一个 Map-Reduce 的解决方法,并使用 Map (parallel_for) 后跟一个 for 循环来执行 Reduce 部分。这样可行。我会尝试另一个 parallel_for 看看是否有效。
    • parallel_reduce 中没有错误(在这种情况下) - 请参阅 my answer
    【解决方案3】:

    您正在从多个线程访问std::vector,这是不安全的。这可能是您问题的根源。将您的 moveScores 容器更改为 tbb::concurrent_vector&lt;int&gt;

    【讨论】:

    • 每个线程都会获得向量索引的独立范围。所以这应该不是问题。矢量不会调整大小,因为如果这样做会导致问题。我会尝试使用并发向量,看看它是否有帮助。谢谢!!!
    • 我尝试了 concurrent_vector,但得到的结果与 std::vector 相同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-11
    • 2016-02-06
    • 2017-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多