从c++数组中高效地实现矩阵
Eigen: Efficient implementation of matrix from C++ array
是否有可能实现一个接收c风格指针作为模板参数的类,并以某种方式解析为静态特征矩阵,但使用提供的内存?假设声明看起来像这样:
EIGEN_ALIGN16 double array[9];
CMatrix<double,3,3,array> :: m;
我确实知道映射,但是我下面提供的示例代码已经证明,与静态特征矩阵相比,它们要慢20%。
这些是前提:
- 我需要提供自己的C指针。这样我就可以有效地重用C代码,而不会产生副本。
- 生成的矩阵在Eigen看来应该是静态的,这样Eigen就可以在编译时使用静态数组进行优化。看看上面的例子,在编译时,我将提供矩阵大小(静态)和C指针。
- CMatrix 应该回落到 Eigen::Matrix。当没有为C数组提供额外的模板参数时,我会得到正常的特征矩阵。
- 我不打算做一个完整的特征扩展。我的意思是我不关心各种各样的检查,为其他用户提供一个整洁的扩展。我只想要一个最有效的解决方案。
是否有可能通过添加新的构造函数来实现解决方案?比如:
EIGEN_ALIGN16 double data[9];
Eigen::Matrix<double,3,3> m(data); //where data is NOT copied but used to replace the static allocation used by default.
找到下面我的代码对基准映射与矩阵效率。它是自包含的,您可以使用
进行编译:g++ -Ofast -DNDEBUG -DEIGEN_NO_MALLOC -I/path_to_my_Eigen benchmark1.cpp -o benchmark1 -lrt
代码如下:
#include <Eigen/Eigen>
#include <bench/BenchTimer.h>
#include <iostream>
using namespace Eigen;
using namespace std;
//#define CLASSIC_METHOD
#define USE_MAPS
EIGEN_DONT_INLINE void classic(double VO[4], double AT[4][4], double VI[4])
{
for (int ii=0; ii<4; ii++)
{
VO[ii] = AT[ii][0] * VI[0] +
AT[ii][1] * VI[1] +
AT[ii][2] * VI[2] +
AT[ii][3] * VI[3];
}
};
template <typename OutputType, typename MatrixType, typename VectorType>
EIGEN_DONT_INLINE void modern(MatrixBase<OutputType>& VOE, const MatrixBase<MatrixType>& A44, const MatrixBase<VectorType>& VIE)
{
VOE.noalias() = A44.transpose()*VIE;
};
int main()
{
EIGEN_ALIGN16 double AT[4][4] = {0.1, 0.2, 0.3, 2.0, 0.2, 0.3, 0.4, 3.0, 0.3, 0.4, 0.5, 4.0, 0.0, 0.0, 0.0, 1.0};
EIGEN_ALIGN16 double VI[4] = {1, 2, 3, 4};
EIGEN_ALIGN16 double VO[4];
//Eigen matrices
#ifndef USE_MAPS
Matrix4d A44 = Matrix4d::MapAligned(AT[0]);
Vector4d VIE = Vector4d::MapAligned(VI);
Vector4d VOE(0,0,0,0);
#else
Map<Matrix4d,Aligned> A44(AT[0]);
Map<Vector4d,Aligned> VIE(VI);
Map<Vector4d,Aligned> VOE(VO);
// Map<Matrix4d> A44(AT[0]);
// Map<Vector4d> VIE(VI);
// Map<Vector4d> VOE(VO);
#endif
#ifdef EIGEN_VECTORIZE
cout << "EIGEN_VECTORIZE defined" << endl;
#else
cout << "EIGEN_VECTORIZE NOT defined" << endl;
#endif
cout << "VIE:" << endl;
cout << VIE << endl;
VI[0] = 3.14;
cout << "VIE:" << endl;
cout << VIE << endl;
BenchTimer timer;
const int num_tries = 5;
const int num_repetitions = 200000000;
#ifdef CLASSIC_METHOD
BENCH(timer, num_tries, num_repetitions, classic(VO, AT, VI));
std::cout << Vector4d::MapAligned(VO) << std::endl;
#else
BENCH(timer, num_tries, num_repetitions, modern(VOE, A44, VIE));
std::cout << VOE << std::endl;
#endif
double elapsed = timer.best();
std::cout << "elapsed time: " << elapsed*1000.0 << " ms" << std::endl;
return 0;
}
有点跑题,但既然你强调了性能:
Eigen汇编并不总是最优的——由于寄存器重用和写回内存不好,会有一些开销(无论如何,这不是归咎于Eigen——在泛型模板中这样做是不可能的任务)。
如果你的内核相当简单(QCD?),我会手工编写汇编(使用内在函数)。
这是用intrinsic重写的经典内核,比Eigen版本更快,对于Map/Matrix类型也是如此(所以你不必发明自己的类型)。
EIGEN_DONT_INLINE void classic(double * __restrict__ VO, const double * __restrict__ AT, const double * __restrict__ VI) {
__m128d vi01 = _mm_load_pd(VI+0);
__m128d vi23 = _mm_load_pd(VI+2);
for (int i = 0; i < 4; i += 2) {
__m128d v00, v11;
// v[i+0,i+0]
{
int ii = i*4;
__m128d at01 = _mm_load_pd(&AT[ii + 0]);
__m128d at23 = _mm_load_pd(&AT[ii + 2]);
v00 = _mm_mul_pd(at01, vi01);
v00 = _mm_add_pd(v00, _mm_mul_pd(at23, vi23));
}
// v[i+1,i+1]
{
int ii = i*4 + 4;
__m128d at01 = _mm_load_pd(&AT[ii + 0]);
__m128d at23 = _mm_load_pd(&AT[ii + 2]);
v11 = _mm_mul_pd(at01, vi01);
v11 = _mm_add_pd(v11, _mm_mul_pd(at23, vi23));
}
__m128d v = _mm_hadd_pd(v00, v11);
// v is now [v00[0] + v00[1], v11[0] + v11[1]]
_mm_store_pd(VO+i, v);
// VO[i] += AT[j+0 + i*4]*VI[j+0];
// VO[i] += AT[j+1 + i*4]*VI[j+1];
}
}
您可以通过交叉加载和多/添加来获得一些额外的改进-我试图保持简单。
结果如下:
g++ -Ofast -DNDEBUG -DEIGEN_NO_MALLOC -DCLASSIC_METHOD -I /usr/local/eigen benchmark1.cpp -o benchmark1 -lrt -msse4; ./benchmark1
elapsed time: 611.397 ms
g++ -Ofast -DNDEBUG -DEIGEN_NO_MALLOC -DCLASSIC_METHOD -DUSE_MAPS -I /usr/local/eigen benchmark1.cpp -o benchmark1 -lrt -msse4; ./benchmark1
elapsed time: 615.541 ms
g++ -Ofast -DNDEBUG -DEIGEN_NO_MALLOC -DUSE_MAPS -I /usr/local/eigen benchmark1.cpp -o benchmark1 -lrt -msse4; ./benchmark1
elapsed time: 981.941 ms
g++ -Ofast -DNDEBUG -DEIGEN_NO_MALLOC -I /usr/local/eigen benchmark1.cpp -o benchmark1 -lrt -msse4; ./benchmark1
elapsed time: 838.852 ms
进一步注意,如果您的矩阵被调换,您可能会编写一个更好的simd内核-水平加法(_mm_hadd_pd
)是昂贵的。
在注释中添加讨论:在函数内部移动特征映射可以消除map和矩阵参数之间的时间差异。
EIGEN_DONT_INLINE void mapped(double (&VO)[4], const double (&AT)[4][4], const double (&VI)[4]) {
Map<const Matrix4d,Aligned> A44(&AT[0][0]);
Map<const Vector4d,Aligned> VIE(VI);
Map<Vector4d,Aligned> VOE(VO);
VOE.noalias() = A44.transpose()*VIE;
}
当将Map传递给函数(非内联函数)时,这是程序集的顶部
movq (%rsi), %rcx
movq (%rdx), %rax
movq (%rdi), %rdx
movapd (%rcx), %xmm0
movapd 16(%rcx), %xmm1
mulpd (%rax), %xmm0
mulpd 16(%rax), %xmm1
与传递数组引用(其中包含映射)或矩阵
相比 movapd (%rsi), %xmm0
movapd 16(%rsi), %xmm1
mulpd (%rdx), %xmm0
mulpd 16(%rdx), %xmm1
相关文章:
- 如何实现高效的算法来计算大型数据集的多个不同值?
- 如何实现四个 i8 元素组的高效_mm256_madd_epi8点积
- 对象内部有大量数据容器,实现更高效的对象交换
- 如何打包结构的成员以实现高效的跨平台代码
- 为什么C++不使用集中存储类型信息以实现高效的 RTTI
- 如何高效实现任意序列的按位旋转?
- 如何在模板化列表类中实现高效添加C++
- 高效实现二进制搜索
- 清晰高效的三维测距树实现
- 实现用于在图形中存储边缘的高效容器
- 关于学习C++编码以实现高效/高性能数学例程,有哪些(推荐的)资源/书籍
- 高效的无符号到签名转换,避免实现定义的行为
- 在Haskell中使用O(1)元素访问实现高效的类似拉链的数据结构
- 如何实现高效的C++运行时统计信息
- 如何使用OpenCV在C++中实现高效的im2col函数
- 在C++表达式模板编程中是否可能实现高效的"repeatedly used intermediates"?
- 在c++中实现高效多线程文件I/O
- c++11结合了std::tuple和std::tie来实现高效排序
- 可视化 如何在没有正则表达式的情况下实现C++高效的全词字符串替换
- visual studio-如何设置VS2008以实现高效的C++开发