c++ 链接操作时特征非常慢

5vf7fwbs  于 2023-01-28  发布在  其他
关注(0)|答案(1)|浏览(133)

在尝试计算大型矩阵中行向量的方差时,我注意到Eigen有一个奇怪的行为。如果我将所有需要的操作链接起来,我会得到非常慢的性能,同时计算一个部分结果,然后执行完全相同的操作,会得到更快的结果。这种行为似乎实际上违背了Eigen文档/FAQ,其中提到要避免临时性。
所以我的问题是,在库中是否有某种我应该避免的已知陷阱,以及如何发现可能发生这种类型的慢下来的情况。
这是我用来测试它的代码。我试着用MSVC编译它(-O2优化)和MinGW GCC“使用部分求值的行方差”版本在GCC下运行大约560 ms,在MSVC下运行大约1 s,而不使用部分求值的版本在GCC下运行大约90 s,在MSVC下运行大约104 s,一个非常荒谬的区别,我没有尝试过,但是我想即使是一系列简单的for循环也会比90秒快得多...

#include <iostream>
#include <vector>
#include <chrono>
#include <random>
#include <functional>

#include "Eigen/Dense"

void printTimespan(std::chrono::nanoseconds timeSpan)
{
    using namespace std::chrono;
    std::cout << "Timing ended:\n"
        << "\t ms: " << duration_cast<milliseconds>(timeSpan).count() << '\n'
        << "\t us: " << duration_cast<microseconds>(timeSpan).count() << '\n'
        << "\t ns: " << timeSpan.count() << '\n';
}

class Timer
{
    std::chrono::steady_clock::time_point start_;
public:
    void start()
    {
        start_ = std::chrono::steady_clock::now();
    }
    void stop()
    {
        timings.push_back((std::chrono::steady_clock::now() - start_).count());
    }
    std::vector<long long> timings;
};

std::vector<float> buildBuffer(size_t rows, size_t cols)
{
    std::vector<float> buffer;
    buffer.reserve(rows * cols);
    for (size_t i = 0; i < rows; i++)
    {
        for (size_t j = 0; j < cols; j++)
        {
            buffer.push_back(std::rand() % 1000);
        }
    }
    return buffer;
}

using EigenArr = Eigen::Array<float, -1, -1, Eigen::RowMajor>;
using EigenMap = Eigen::Map<EigenArr>;

std::vector<float> benchmark(std::function<EigenArr(const EigenMap&)> func)
{
    constexpr size_t rows = 2000, cols = 200, repetitions = 1000;
    std::vector<float> buffer = buildBuffer(rows, cols);
    EigenMap map(buffer.data(), rows, cols);
    EigenArr res;
    std::vector<float> means; //just to prevent the compiler from not computing anything because the results aren't used

    Timer timer;
    for (size_t i = 0; i < repetitions; i++)
    {
        timer.start();
        res = func(map);
        timer.stop();
        means.push_back(res.mean());
    }

    Eigen::Map<Eigen::Vector<long long, -1>> timingsMap(timer.timings.data(), timer.timings.size());
    printTimespan(std::chrono::nanoseconds(timingsMap.sum()));
    return means;
}

int main()
{
    std::cout << "mean center rows\n";
    benchmark([](const EigenMap& map)
    {
        return (map.colwise() - map.rowwise().mean()).eval(); 
    });

    std::cout << "squared deviations\n";
    benchmark([](const EigenMap& map)
    {
        return (map.colwise() - map.rowwise().mean()).square().eval();
    });

    std::cout << "row variance with partial eval\n";
    benchmark([](const EigenMap& map)
    {
        EigenArr partial = (map.colwise() - map.rowwise().mean()).square().eval();
        return (partial.rowwise().sum() / (map.cols() - 1)).eval();
    });

    std::cout << "row variance\n";
    benchmark([](const EigenMap& map)
    {
        return ((map.colwise() - map.rowwise().mean()).square().rowwise().sum() / (map.cols() - 1)).eval();
    });
}
y3bcpkx1

y3bcpkx11#

我怀疑它是慢一点的双rowwise()。
Eigen中的很多操作都是按需计算的,并且不会创建临时数据。这样做是为了防止不必要的数据副本。但我怀疑每次外部rowwise()被请求一个元素时,它都在计算内部部分,将操作数平方。通过保存一次副本,它可以防止每个单元格被多次计算。
您也可以在square()之后调用.eval(),从而在一行中完成此操作。
另一种可能性是缓存问题,如果它被迫在内存中跳来跳去很多。

相关问题