复制 MatrixXf 会导致 Eigen 数学失败。发布版和调试版提供不同的结果

问题描述 投票:0回答:1

在创建用于获取行平均值的矩阵xf 的副本时遇到错误。它似乎在调试中生成非常大的值,在发布中生成零值。在这两种情况下,答案都是错误的,因为平均值是 0.5。如果我不使用额外的行来创建输入副本,则错误就会消失并且输出将按预期进行。为什么会出现这种情况?

我在 cpp 中创建了一个最小的示例。我在 vs2022 中使用 cl.exe 和 cmake 编译它。

int main() {
    auto startTime = std::chrono::high_resolution_clock::now();
    #ifdef NDEBUG
        std::cout << "Release Build" << std::endl;
    #else
        std::cout << "Debug Build" << std::endl;
    #endif

    std::cout << "Eigen world version: " << EIGEN_WORLD_VERSION << "\n";
    std::cout << "Eigen major version: " << EIGEN_MAJOR_VERSION << "\n";
    std::cout << "Eigen minor version: " << EIGEN_MINOR_VERSION << "\n";
    std::cout << "cpp version: " << __cplusplus << "\n";
    
    Eigen::setNbThreads(1);
    std::cout << "Eigen threads: " << Eigen::nbThreads() << "\n";

    Eigen::MatrixXf inputs(2, 4);
    inputs <<
        1, 0, 1, 0,
        1, 1, 0, 0;

    Eigen::MatrixXf calcNextLine = inputs;
    calcNextLine = calcNextLine.rowwise().mean();
    Eigen::MatrixXf calcSameLine = inputs.rowwise().mean();

    std::cout << "\ninputs = \n" << inputs << "\n";
    std::cout << "\ncalcNextLine = \n" << calcNextLine << "\n";
    std::cout << "\ncalcSameLine = \n" << calcSameLine << "\n";

    auto currentTime = std::chrono::high_resolution_clock::now();
    auto deltaTime = std::chrono::duration_cast<std::chrono::nanoseconds>(currentTime - startTime).count();
    std::cout << "\nFinished in " << (deltaTime / 1.0e6) << " milliseconds.\n";

    return 0;
}

调试中的控制台输出:

Debug Build
Eigen world version: 3
Eigen major version: 4
Eigen minor version: 0
cpp version: 199711
Eigen threads: 1

inputs =
1 0 1 0
1 1 0 0

calcNextLine =
-1.07901e+08
-1.07901e+08

calcSameLine =
0.5
0.5

Finished in 4.2896 milliseconds.

发布中的控制台输出:

Release Build
Eigen world version: 3
Eigen major version: 4
Eigen minor version: 0
cpp version: 199711
Eigen threads: 1

inputs =
1 0 1 0
1 1 0 0

calcNextLine =
0
0

calcSameLine =
0.5
0.5

Finished in 3.6746 milliseconds.
c++ math matrix eigen
1个回答
0
投票

如果我不使用额外的行来创建输入副本,则错误就会消失并且输出将按预期进行。为什么会出现这种情况?

这不是副本。以下是该特定行中发生的情况:

calcNextLine = calcNextLine.rowwise().mean();
  1. 大多数 Eigen 操作都会创建代理对象,以便可以将它们的计算串在一起形成单个计算,而无需分配临时内存。因此
    rowwise().mean()
    创建一个表示部分归约的对象,该对象 引用
    calcNextLine
    矩阵
  2. 您将此表达式对象分配给
    calcNextLine
    本身,调用其赋值运算符
  3. 赋值运算符发现分配的对象具有不同的大小(因为您减少了行),因此它重新分配到新的大小
  4. 这会释放行平均表达式对象引用的内存。这会创建一个悬挂指针(或者可能是使用新分配的原始大小进行越界访问,我必须检查该指针的源)
  5. 当赋值运算符将平均值计算到新分配的数组中时,将调用未定义的行为。任何事情都可能发生

我很确定有关别名问题的 Eigen 文档页面解释了这些内容,但该网站目前(再次)不可用。一般来说,只有在以下情况下,才可以在 Eigen 中的赋值左侧和右侧之间使用别名:

  1. 右边是矩阵乘法,当然除了
    left_side.noalias() = right_side
  2. 右侧是一个简单的逐元素操作,没有大小变化并且它是一个直接别名,不以任何其他方式重叠。这没关系:
    a = a + b
    。这不行:
    a.topRows(N) = a.middleRows(1, N) + b

无论如何,你如何解决这个问题?两种选择:

  1. 使用
    eval()
    创建一个新向量,然后将其分配给其输入
calcNextLine = calcNextLine.rowwise().mean().eval();
  1. 使用单独的变量
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.