正如docs 中所解释的auto
,在使用带有 eigen 的类型推导时应该注意。例如,以下函数对我来说是段错误,我可以接受:
struct MultiplyAdd{
Eigen::Matrix<double, DIMS, DIMS> mult;
Eigen::Matrix<double, DIMS, 1> add;
auto counterExample(Eigen::Matrix<double, DIMS, -1> const& data) const {
return (mult.sinh()*data).colwise() + add;
}
};
据我了解上面的文档,我可以将返回类型拼写为实际矩阵(而不是惰性求值表达式)或使用eval()
.
#include <benchmark/benchmark.h>
#include <Eigen/Dense>
constexpr int DIMS = 10;
struct MultiplyAdd{
Eigen::Matrix<double, DIMS, DIMS> mult;
Eigen::Matrix<double, DIMS, 1> add;
Eigen::Matrix<double, DIMS, -1> transform(Eigen::Matrix<double, DIMS, -1> const& data) const {
return ((mult*data).colwise() + add).eval();
}
};
struct classic_worker{
static auto dowork(Eigen::Matrix<double, DIMS, -1> const& data, MultiplyAdd const& trafo) {
const auto flags = ((2.*trafo.transform(data)).row(2).array() > 1.7f).eval();
return flags;
}
};
struct allinline{
static auto dowork(Eigen::Matrix<double, DIMS, -1> const& data, MultiplyAdd const& trafo) {
const auto flags = ((2.*((trafo.mult * data).colwise() + trafo.add)).row(2).array() > 1.7f).eval();
return flags;
}
};
template <typename STRUCT>
static void quicktest(benchmark::State &state) {
Eigen::Matrix<double, DIMS, -1> data = Eigen::MatrixXd::Random(DIMS, state.range(0));
Eigen::Matrix<double, DIMS, DIMS> m = Eigen::MatrixXd::Random(DIMS, DIMS);
Eigen::Matrix<double, DIMS, 1> a = Eigen::MatrixXd::Random(DIMS, 1);
MultiplyAdd trafo{m,a};
for (auto _ : state) {
benchmark::DoNotOptimize(STRUCT::dowork(data, trafo));
}
}
// clang-format off
BENCHMARK_TEMPLATE(quicktest, classic_worker )->UseRealTime()->DenseRange(20,320, 50);
BENCHMARK_TEMPLATE(quicktest, allinline )->UseRealTime()->DenseRange(20,320, 50);
// clang-format on
BENCHMARK_MAIN();
在这个较长的示例中,我有两个我的实现MultiplyAdd
,并将它们与 google-benchmark 进行比较。在一种情况下transform
,我使用可以放入中央标题的方法进行操作,在另一种情况下,我在“调用站点”上进行所有低级数学运算。在示例中,我仅使用计算的第 2 行。它出现
./iter_5 --benchmark_filter='.*(classic|all).*320.*' --benchmark_repetitions=10 --benchmark_report_aggregates_only
...
-----------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
-----------------------------------------------------------------------------------------
quicktest<classic_worker>/320/real_time_mean 3974 ns 3974 ns 10
quicktest<classic_worker>/320/real_time_median 3944 ns 3944 ns 10
quicktest<classic_worker>/320/real_time_stddev 121 ns 121 ns 10
quicktest<allinline>/320/real_time_mean 3308 ns 3308 ns 10
quicktest<allinline>/320/real_time_median 3285 ns 3285 ns 10
quicktest<allinline>/320/real_time_stddev 68.2 ns 68.2 ns 10
all-at-call-site 版本的速度提高了约 20%(包含所有微基准测试的警告)。
鉴于上述链接,我的天真解释是我正在避免惰性评估并不必要地计算所有行。(速度差异似乎与我在 3 到 100 之间的选择无关DIMS
,所以我怀疑我的解释是错误的。)
编写返回特征矩阵类型的函数的正确模式是什么(在文档中我只看到提到函数参数)?
编辑:我有一个新的例子,在观察上更接近我的真实案例
#include <benchmark/benchmark.h>
#include <Eigen/Dense>
struct Trafo{
Eigen::Quaternion<double> mult;
Eigen::Matrix<double, 3, 1> add;
inline Eigen::Matrix<double, 3, -1> transform(Eigen::Matrix<double, 3, -1> const& data) const noexcept {
return ((mult.toRotationMatrix()*data).colwise() + add).eval();
}
inline auto counterExample(Eigen::Matrix<double, 3, -1> const& data) const noexcept {
return (mult.toRotationMatrix()*data).colwise() + add;
}
};
struct classic_worker{
static auto dowork(Eigen::Matrix<double, 3, -1> const& data, Trafo const& trafo) {
const auto flags = ((2.*trafo.transform(data)).row(2).array() > 1.7f).eval();
return flags;
}
};
struct counterExample_worker{
static auto dowork(Eigen::Matrix<double, 3, -1> const& data, Trafo const& trafo) {
const auto flags = ((2.*trafo.counterExample(data)).row(2).array() > 1.7f).eval();
return flags;
}
};
struct allinline{
static auto dowork(Eigen::Matrix<double, 3, -1> const& data, Trafo const& trafo) {
const auto flags = ((2.*((trafo.mult.toRotationMatrix() * data).colwise() + trafo.add)).row(2).array() > 1.7f).eval();
return flags;
}
};
template <typename STRUCT>
static void quicktest(benchmark::State &state) {
Eigen::Matrix<double, 3, -1> data = Eigen::MatrixXd::Random(3, state.range(0));
Eigen::Matrix<double, 4, 1> random = Eigen::MatrixXd::Random(4, 1);
Eigen::Quaternion<double> m{random(0), random(1), random(2), random(3)};
Eigen::Matrix<double, 3, 1> a = Eigen::MatrixXd::Random(3, 1);
Trafo trafo{m,a};
for (auto _ : state) {
benchmark::DoNotOptimize(STRUCT::dowork(data, trafo));
}
}
// clang-format off
BENCHMARK_TEMPLATE(quicktest, counterExample_worker )->UseRealTime()->DenseRange(20,320, 50);
BENCHMARK_TEMPLATE(quicktest, classic_worker )->UseRealTime()->DenseRange(20,320, 50);
BENCHMARK_TEMPLATE(quicktest, allinline )->UseRealTime()->DenseRange(20,320, 50);
// clang-format on
BENCHMARK_MAIN();
有三种方法可以进行我的计算:
- 一个不调用的函数(
counterExample
被调用),因此让调用站点负责评估 - 尽管这可能(并且似乎)导致低效的多次评估counterExample_worker
eval
- 调用并评估其表达式的函数(
transform
被调用) (可能不需要)classic_worker
eval
- 呼叫现场的所有代码(由 完成
allinline
)
用 g++8 编译并且-O3 -march=native
(并且都在同一个翻译单元中,所以编译器可以做它想要的所有内联)我看到下面的时序表
quicktest<counterExample_worker>/320/real_time_mean 52612 ns 52612 ns 10
quicktest<counterExample_worker>/320/real_time_median 55218 ns 55217 ns 10
quicktest<counterExample_worker>/320/real_time_stddev 8501 ns 8501 ns 10
quicktest<classic_worker>/320/real_time_mean 622 ns 622 ns 10
quicktest<classic_worker>/320/real_time_median 619 ns 619 ns 10
quicktest<classic_worker>/320/real_time_stddev 6.89 ns 6.89 ns 10
quicktest<allinline>/320/real_time_mean 428 ns 428 ns 10
quicktest<allinline>/320/real_time_median 426 ns 426 ns 10
quicktest<allinline>/320/real_time_stddev 5.68 ns 5.67 ns 10
看来,eval
在这里不打电话是个坏主意。然而,在它自己的功能中进行转换也需要将所有内容写在一行中。所以问题是:有没有办法在不降低运行时减速的情况下将这里的转换放入单独的函数中?我确实注意到(不幸的是)此示例中的结果在很大程度上取决于编译器和编译标志(andg++-8 -msse3
之间的差异消失了,似乎提供了更快的 with和 with ...)allinline
classic_worker
clang++-10
allinline
-msse3
-march=native