【问题标题】:Lambda function type creepLambda 函数类型蠕变
【发布时间】:2016-09-09 18:49:48
【问题描述】:

让我们看看下面的代码:

tbb::blocked_range<int> range(0, a.rows);
uint64_t positive = tbb::parallel_reduce(range, 0, // <- initial value
  [&](const tbb::blocked_range<int>& r, uint64_t v)->uint64_t {
    for (int y = r.begin(); y < r.end(); ++y) {
        auto rA = a[y], rB = b[y];
        for (int x = 0; x < a.cols; ++x) {
            auto A = rA[x], B = rB[x];
            for (int l = y; l < a.rows; ++l) {
                auto rAA = a[l], rBB = b[l];
                for (int m = x; m < a.cols; ++m) {
                    if (l == y && m == x)
                        continue;
                    auto AA = rAA[m], BB = rBB[m];
                    if ((A == AA) && (B == BB))
                        v++; // <- value is changed
                    if ((A != AA) && (B != BB))
                        v++; // <- value is changed
                }
            }
        }
    }
    return v;
}, [](uint64_t first, uint64_t second)->uint64_t {
    std::cerr << first << ' + ' << second;  // <- wrong values occur
    return first+second;
}
);

这是一个并行reduce操作,初始值为0。然后,在每个并行计算中,基于初始值,我们向上计数(第一个lambda函数中的局部变量v)。第二个 lambda 函数聚合来自并行工作者的结果。

有趣的是,这段代码没有按预期工作。第二个 lambda 函数的输出将显示整数溢出导致的大量数字。

将第二行替换为:

uint64_t positive = tbb::parallel_reduce(range, (uint64_t)0, // <- initial value

现在我想知道。第一个 lambda (uint64_t v) 的定义不会强制执行这种强制转换吗?应该在 uint64_t 上运行的函数如何改为在 int 上运行?

编译器是 GCC 6。

【问题讨论】:

  • 0int...
  • 当然。但是参数vuint64_t

标签: c++ lambda casting type-conversion


【解决方案1】:

lambda 采用什么参数并不重要。根据the docs,一切都基于第二个参数的类型:

template<typename Range, typename Value,
         typename Func, typename Reduction>
Value parallel_reduce( const Range& range, const Value& identity,
                       const Func& func, const Reduction& reduction,
                       [, partitioner[, task_group_context& group]] );

带有以下伪签名:

Value Func::operator()(const Range& range, const Value& x)
Value Reduction::operator()(const Value& x, const Value& y)

所以Value 被传递到FuncReduction 并返回。如果您想在任何地方使用uint64_ts,您需要确保Valueuint64_t。这就是为什么您的 (uint64_t)0 有效但您的 0 无效的原因(实际上是未定义的启动行为)。


请注意,这与普通accumulate 会遇到的问题相同:

std::vector<uint64_t> vs{0x7fffffff, 0x7fffffff, 0x7fffffff};
uint64_t sum = std::accumulate(vs.begin(), vs.end(), 0, std::plus<uint64_t>{});
                                //                  ^^^ oops, int 0!
                                //           even though I'm using plus<uint64_t>!
assert(sum == 0x17ffffffd);     // fails because actually sum is truncated
                                // and is just 0x7ffffffd

【讨论】:

  • 其实上面例子的结果是UB(unsigned -> signed with overflow就是UB)。
  • 所以简单地总结一下,lambda 函数实际上是在 uint64_t 上计算的,但是它被包装在一个隐式定义在 int 上的接口中,因此发生了转换为 int在返回第一个 lambda 并将 int 传递给第二个 lambda,然后重铸为 uint64_t,但损坏已经造成。
  • @ypnos 是的,基本上就是这样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-23
  • 2019-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多