Eigen:对于这个模板表达式,为什么 Map 比 Vector3d 慢?
Eigen: Why is Map slower than Vector3d for this template expression?
我在 x、y、z 模式的 std::vector<double>
中有一个点云,以及一个 std::vector<int>
索引,其中连续整数的每个三元组是一张脸的连通性。基本上是一个简单的三角形网格数据结构。
我必须计算所有面孔的面积,我正在对几种方法进行基准测试:
我可以像这样将数据块包装在 Eigen::Map<const Eigen::Vector3d>
中:
static void face_areas_eigenmap(const std::vector<double>& V,
const std::vector<int>& F,
std::vector<double>& FA) {
// Number of faces is size / 3.
for (auto f = 0; f < F.size() / 3; ++f) {
// Get vertex indices of face f.
auto v0 = F[f * 3];
auto v1 = F[f * 3 + 1];
auto v2 = F[f * 3 + 2];
// View memory at each vertex position as a vector.
Eigen::Map<const Eigen::Vector3d> x0{&V[v0 * 3]};
Eigen::Map<const Eigen::Vector3d> x1{&V[v1 * 3]};
Eigen::Map<const Eigen::Vector3d> x2{&V[v2 * 3]};
// Compute and store face area.
FA[f] = 0.5 * (x1 - x0).cross(x2 - x0).norm();
}
}
或者我可以选择这样创建 Eigen::Vector3d
:
static void face_areas_eigenvec(const std::vector<double>& V,
const std::vector<int>& F,
std::vector<double>& FA) {
for (auto f = 0; f < F.size() / 3; ++f) {
auto v0 = F[f * 3];
auto v1 = F[f * 3 + 1];
auto v2 = F[f * 3 + 2];
// This is the only change, swap Map for Vector3d.
Eigen::Vector3d x0{&V[v0 * 3]};
Eigen::Vector3d x1{&V[v1 * 3]};
Eigen::Vector3d x2{&V[v2 * 3]};
FA[f] = 0.5 * (x1 - x0).cross(x2 - x0).norm();
}
}
最后我也在考虑带有显式叉积和范数的硬编码版本:
static void face_areas_ptr(const std::vector<double>& V,
const std::vector<int>& F, std::vector<double>& FA) {
for (auto f = 0; f < F.size() / 3; ++f) {
const auto* x0 = &V[F[f * 3] * 3];
const auto* x1 = &V[F[f * 3 + 1] * 3];
const auto* x2 = &V[F[f * 3 + 2] * 3];
std::array<double, 3> s0{x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
std::array<double, 3> s1{x2[0] - x0[0], x2[1] - x0[1], x2[2] - x0[2]};
std::array<double, 3> c{s0[1] * s1[2] - s0[2] * s1[1],
s0[2] * s1[0] - s0[0] * s1[2],
s0[0] * s1[1] - s0[1] * s1[0]};
FA[f] = 0.5 * std::sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]);
}
}
我已经对这些方法进行了基准测试,使用 Eigen::Map
的版本总是最慢的,尽管它与使用 Eigen::Vector3d
的版本做同样的事情,但我期望性能没有变化,因为地图是基本上是一个指针。
-----------------------------------------------------------------
Benchmark Time CPU Iterations
-----------------------------------------------------------------
BM_face_areas_eigenvec 59757936 ns 59758018 ns 11
BM_face_areas_ptr 58305018 ns 58304436 ns 11
BM_face_areas_eigenmap 62356850 ns 62354710 ns 10
我试过使用与指针版本相同的代码在地图版本中切换特征模板表达式:
std::array<double, 3> s0{x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
std::array<double, 3> s1{x2[0] - x0[0], x2[1] - x0[1], x2[2] - x0[2]};
std::array<double, 3> c{s0[1] * s1[2] - s0[2] * s1[1],
s0[2] * s1[0] - s0[0] * s1[2],
s0[0] * s1[1] - s0[1] * s1[0]};
FA[f] = 0.5 * std::sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]);
神奇的是时间是可比较的:
-----------------------------------------------------------------
Benchmark Time CPU Iterations
-----------------------------------------------------------------
BM_face_areas_array 58967864 ns 58967891 ns 11
BM_face_areas_ptr 60034545 ns 60034682 ns 11
BM_face_areas_eigenmap 60382482 ns 60382027 ns 11
Eigen 表达式中 Eigen::Map
有什么问题需要注意吗?
查看编译器输出,似乎第二个版本通过将其中一些内存负载聚合到矢量负载中,使编译器发出更少的内存负载。
https://godbolt.org/z/qs38P41eh
cross
的 Eigen 代码不包含任何显式矢量化。这取决于编译器能否很好地处理它。而且由于您在表达式(减法)上调用 cross,编译器会过早放弃。基本上是编译器没有找到相同优化的错。
您的第三个代码与第二个代码的工作方式相同,因为编译器将减法(创建 s0 和 s1)识别为它可以执行矢量化的操作,从而生成等效代码。如果你这样做,你可以用 Eigen 达到同样的效果:
Eigen::Map<const Eigen::Vector3d> x0{&V[v0 * 3]};
Eigen::Map<const Eigen::Vector3d> x1{&V[v1 * 3]};
Eigen::Map<const Eigen::Vector3d> x2{&V[v2 * 3]};
Eigen::Vector3d s0 = x1 - x0;
Eigen::Vector3d s1 = x2 - x0;
// Compute and store face area.
FA[f] = 0.5 * s0.cross(s1).norm();
我在 x、y、z 模式的 std::vector<double>
中有一个点云,以及一个 std::vector<int>
索引,其中连续整数的每个三元组是一张脸的连通性。基本上是一个简单的三角形网格数据结构。
我必须计算所有面孔的面积,我正在对几种方法进行基准测试:
我可以像这样将数据块包装在 Eigen::Map<const Eigen::Vector3d>
中:
static void face_areas_eigenmap(const std::vector<double>& V,
const std::vector<int>& F,
std::vector<double>& FA) {
// Number of faces is size / 3.
for (auto f = 0; f < F.size() / 3; ++f) {
// Get vertex indices of face f.
auto v0 = F[f * 3];
auto v1 = F[f * 3 + 1];
auto v2 = F[f * 3 + 2];
// View memory at each vertex position as a vector.
Eigen::Map<const Eigen::Vector3d> x0{&V[v0 * 3]};
Eigen::Map<const Eigen::Vector3d> x1{&V[v1 * 3]};
Eigen::Map<const Eigen::Vector3d> x2{&V[v2 * 3]};
// Compute and store face area.
FA[f] = 0.5 * (x1 - x0).cross(x2 - x0).norm();
}
}
或者我可以选择这样创建 Eigen::Vector3d
:
static void face_areas_eigenvec(const std::vector<double>& V,
const std::vector<int>& F,
std::vector<double>& FA) {
for (auto f = 0; f < F.size() / 3; ++f) {
auto v0 = F[f * 3];
auto v1 = F[f * 3 + 1];
auto v2 = F[f * 3 + 2];
// This is the only change, swap Map for Vector3d.
Eigen::Vector3d x0{&V[v0 * 3]};
Eigen::Vector3d x1{&V[v1 * 3]};
Eigen::Vector3d x2{&V[v2 * 3]};
FA[f] = 0.5 * (x1 - x0).cross(x2 - x0).norm();
}
}
最后我也在考虑带有显式叉积和范数的硬编码版本:
static void face_areas_ptr(const std::vector<double>& V,
const std::vector<int>& F, std::vector<double>& FA) {
for (auto f = 0; f < F.size() / 3; ++f) {
const auto* x0 = &V[F[f * 3] * 3];
const auto* x1 = &V[F[f * 3 + 1] * 3];
const auto* x2 = &V[F[f * 3 + 2] * 3];
std::array<double, 3> s0{x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
std::array<double, 3> s1{x2[0] - x0[0], x2[1] - x0[1], x2[2] - x0[2]};
std::array<double, 3> c{s0[1] * s1[2] - s0[2] * s1[1],
s0[2] * s1[0] - s0[0] * s1[2],
s0[0] * s1[1] - s0[1] * s1[0]};
FA[f] = 0.5 * std::sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]);
}
}
我已经对这些方法进行了基准测试,使用 Eigen::Map
的版本总是最慢的,尽管它与使用 Eigen::Vector3d
的版本做同样的事情,但我期望性能没有变化,因为地图是基本上是一个指针。
-----------------------------------------------------------------
Benchmark Time CPU Iterations
-----------------------------------------------------------------
BM_face_areas_eigenvec 59757936 ns 59758018 ns 11
BM_face_areas_ptr 58305018 ns 58304436 ns 11
BM_face_areas_eigenmap 62356850 ns 62354710 ns 10
我试过使用与指针版本相同的代码在地图版本中切换特征模板表达式:
std::array<double, 3> s0{x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
std::array<double, 3> s1{x2[0] - x0[0], x2[1] - x0[1], x2[2] - x0[2]};
std::array<double, 3> c{s0[1] * s1[2] - s0[2] * s1[1],
s0[2] * s1[0] - s0[0] * s1[2],
s0[0] * s1[1] - s0[1] * s1[0]};
FA[f] = 0.5 * std::sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]);
神奇的是时间是可比较的:
-----------------------------------------------------------------
Benchmark Time CPU Iterations
-----------------------------------------------------------------
BM_face_areas_array 58967864 ns 58967891 ns 11
BM_face_areas_ptr 60034545 ns 60034682 ns 11
BM_face_areas_eigenmap 60382482 ns 60382027 ns 11
Eigen 表达式中 Eigen::Map
有什么问题需要注意吗?
查看编译器输出,似乎第二个版本通过将其中一些内存负载聚合到矢量负载中,使编译器发出更少的内存负载。 https://godbolt.org/z/qs38P41eh
cross
的 Eigen 代码不包含任何显式矢量化。这取决于编译器能否很好地处理它。而且由于您在表达式(减法)上调用 cross,编译器会过早放弃。基本上是编译器没有找到相同优化的错。
您的第三个代码与第二个代码的工作方式相同,因为编译器将减法(创建 s0 和 s1)识别为它可以执行矢量化的操作,从而生成等效代码。如果你这样做,你可以用 Eigen 达到同样的效果:
Eigen::Map<const Eigen::Vector3d> x0{&V[v0 * 3]};
Eigen::Map<const Eigen::Vector3d> x1{&V[v1 * 3]};
Eigen::Map<const Eigen::Vector3d> x2{&V[v2 * 3]};
Eigen::Vector3d s0 = x1 - x0;
Eigen::Vector3d s1 = x2 - x0;
// Compute and store face area.
FA[f] = 0.5 * s0.cross(s1).norm();