在尝试计算大型矩阵中行向量的方差时,我注意到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();
});
}
1条答案
按热度按时间y3bcpkx11#
我怀疑它是慢一点的双rowwise()。
Eigen中的很多操作都是按需计算的,并且不会创建临时数据。这样做是为了防止不必要的数据副本。但我怀疑每次外部rowwise()被请求一个元素时,它都在计算内部部分,将操作数平方。通过保存一次副本,它可以防止每个单元格被多次计算。
您也可以在square()之后调用.eval(),从而在一行中完成此操作。
另一种可能性是缓存问题,如果它被迫在内存中跳来跳去很多。