我已经实现了两个函数来执行两个向量(不是std :: vector)的交叉积,一个是成员函数,另一个是全局函数,这里是关键代码(省略了其他部分)
//for member function
template <typename Scalar>
SquareMatrix<Scalar,3> Vector<Scalar,3>::outerProduct(const Vector<Scalar,3> &vec3) const
{
SquareMatrix<Scalar,3> result;
for(unsigned int i = 0; i < 3; ++i)
for(unsigned int j = 0; j < 3; ++j)
result(i,j) = (*this)[i]*vec3[j];
return result;
}
//for global function: Dim = 3
template<typename Scalar, int Dim>
void outerProduct(const Vector<Scalar, Dim> & v1 , const Vector<Scalar, Dim> & v2, SquareMatrix<Scalar, Dim> & m)
{
for (unsigned int i=0; i<Dim; i++)
for (unsigned int j=0; j<Dim; j++)
{
m(i,j) = v1[i]*v2[j];
}
}
它们几乎相同,只是一个是具有返回值的成员函数,另一个是全局函数,其中计算的值直接分配给方阵,因此不需要返回值。
实际上,我本来是要用全局的一个替换成员,以提高性能,因为第一个就是复制操作。然而,奇怪的是,全局函数的时间成本几乎是成员的两倍。此外,我发现执行
m(i,j) = v1[i]*v2[j]; // in global function
需要比
更多的时间result(i,j) = (*this)[i]*vec3[j]; // in member function
所以问题是,成员和全局功能之间的性能差异是如何产生的?
任何人都能说出原因吗?
希望我已经清楚地提出了我的问题,并对我可怜的英语表示抱歉!
// --------------------------------------------- -------------------------------------------
更多信息:
以下是我用来测试性能的代码:
//the codes below is in a loop
Vector<double, 3> vec1;
Vector<double, 3> vec2;
Timer timer;
timer.startTimer();
for (unsigned int i=0; i<100000; i++)
{
SquareMatrix<double,3> m = vec1.outerProduct(vec2);
}
timer.stopTimer();
std::cout<<"time cost for member function: "<< timer.getElapsedTime()<<std::endl;
timer.startTimer();
SquareMatrix<double,3> m;
for (unsigned int i=0; i<100000; i++)
{
outerProduct(vec1, vec2, m);
}
timer.stopTimer();
std::cout<<"time cost for global function: "<< timer.getElapsedTime()<<std::endl;
std::system("pause");
并捕获结果:
你可以看到成员功能几乎是全球功能的两倍。
另外,我的项目是基于64位Windows系统构建的,实际上代码用于生成基于Scons构造工具的静态lib文件,以及生成的vs2010项目文件。
我必须提醒奇怪的性能差异只发生在发布版本中,而在调试版本类型中,全局函数几乎是成员版本的五倍。(约0.10s vs 0.02s)
答案 0 :(得分:3)
一种可能的解释:
使用内联,在第一种情况下,编译器可能知道result(i, j)
(来自局部变量)不是别名this[i]
或vec3[j]
,因此标量数组都不是this
和vec3
的修改。
在第二种情况下,从功能的角度来看,变量可能是别名,因此每次写入m
都可能会修改v1
或v2
的标量,因此v1[i]
都不会1}}也可以缓存v2[j]
。
您可以尝试使用restrict关键字扩展来检查我的假设是否正确。
答案 1 :(得分:2)
编辑:原始程序集中的循环省略已得到纠正
[释义]为什么成员函数和静态函数之间的性能不同?
我将从您提出的问题中提到的最简单的事情开始,逐步进行性能测试/分析的细微差别。
测量调试版本的性能是一个坏主意。编译器在许多地方都采取自由,例如将未初始化的数组归零,生成并非严格必要的额外代码,并且(显然)不经过诸如常数传播之类的微不足道的优化。这导致了下一点......
始终关注程序集。当涉及到性能的微妙之处时,C和C ++是高级语言。许多人甚至认为x86汇编是一种高级语言,因为每个指令在解码期间被分解成可能的几个微操作。你只能通过查看C ++代码来判断计算机在做什么。例如,根据您实现SquareMatrix
的方式,编译器可能会也可能无法在优化期间执行复制省略。
在测试性能时输入一些更细微的主题...
确保编译器实际上正在生成循环。使用示例测试代码,g ++ 4.7.2实际上并不生成我的SquareMatrix
和Vector
实现的循环。我实现了它们以将所有组件初始化为0.0
,因此编译器可以静态地确定值永远不会改变,因此只生成一组mov
指令而不是循环。 在我的示例代码中,我在循环中使用COMPILER_NOP
(gcc)为__asm__ __volatile__("":::)
来防止这种情况(因为编译器无法预测手动汇编的副作用,因此无法忽略循环编辑:我使用COMPILER_NOP
但由于函数的输出值从未使用过,编译器仍然可以从循环中删除大部分工作,并将循环减少到此:
.L7
subl $1, %eax
jne .L7
我已经通过在循环中执行其他操作来纠正这个问题。循环现在从输出中为输入分配一个值,防止这种优化并强制循环覆盖最初的预期。< / p>
要(最后)回答你的问题:当我实现了运行代码所需的其余部分,并通过检查程序集实际生成循环来验证时,这两个函数执行相同的时间。 他们甚至在汇编中具有几乎相同的实现。
这是成员函数的程序集:
movsd 32(%rsp), %xmm7
movl $100000, %eax
movsd 24(%rsp), %xmm5
movsd 8(%rsp), %xmm6
movapd %xmm7, %xmm12
movsd (%rsp), %xmm4
movapd %xmm7, %xmm11
movapd %xmm5, %xmm10
movapd %xmm5, %xmm9
mulsd %xmm6, %xmm12
mulsd %xmm4, %xmm11
mulsd %xmm6, %xmm10
mulsd %xmm4, %xmm9
movsd 40(%rsp), %xmm1
movsd 16(%rsp), %xmm0
jmp .L7
.p2align 4,,10
.p2align 3
.L12:
movapd %xmm3, %xmm1
movapd %xmm2, %xmm0
.L7:
movapd %xmm0, %xmm8
movapd %xmm1, %xmm3
movapd %xmm1, %xmm2
mulsd %xmm1, %xmm8
movapd %xmm0, %xmm1
mulsd %xmm6, %xmm3
mulsd %xmm4, %xmm2
mulsd %xmm7, %xmm1
mulsd %xmm5, %xmm0
subl $1, %eax
jne .L12
和静态函数的程序集:
movsd 32(%rsp), %xmm7
movl $100000, %eax
movsd 24(%rsp), %xmm5
movsd 8(%rsp), %xmm6
movapd %xmm7, %xmm12
movsd (%rsp), %xmm4
movapd %xmm7, %xmm11
movapd %xmm5, %xmm10
movapd %xmm5, %xmm9
mulsd %xmm6, %xmm12
mulsd %xmm4, %xmm11
mulsd %xmm6, %xmm10
mulsd %xmm4, %xmm9
movsd 40(%rsp), %xmm1
movsd 16(%rsp), %xmm0
jmp .L9
.p2align 4,,10
.p2align 3
.L13:
movapd %xmm3, %xmm1
movapd %xmm2, %xmm0
.L9:
movapd %xmm0, %xmm8
movapd %xmm1, %xmm3
movapd %xmm1, %xmm2
mulsd %xmm1, %xmm8
movapd %xmm0, %xmm1
mulsd %xmm6, %xmm3
mulsd %xmm4, %xmm2
mulsd %xmm7, %xmm1
mulsd %xmm5, %xmm0
subl $1, %eax
jne .L13
总之:在判断系统的实现是否不同之前,您可能需要稍微加强代码。确保实际生成循环(查看程序集)并查看编译器是否能够从成员函数中删除返回值。
如果这些事情属实,您仍然会看到差异,那么您可以在此处发布SquareMatrix
和Vector
的实施方式,以便我们为您提供更多信息吗?
我的工作示例的完整代码,生成文件和生成的程序集是available as a GitHub gist。
答案 2 :(得分:0)
模板函数的显式实例会产生性能差异吗?
我为寻找性能差异所做的一些实验:
<强> 1 强>
首先,我怀疑性能差异可能是由实现本身引起的。实际上,我们有两组实现,一组由我们自己实现(这一组与@black的代码非常相似),另一组实现为Eigen::Matrix
的包装,由宏控制开 - 关,但是这两个实现之间的切换没有做任何改变,全局一个仍然比成员一个慢。
<强> 2 强>
由于这些代码(类Vector<Scalar, Dim>
&amp; SquareMatrix<Scalar, Dim>
)是在大型项目中实现的,我猜测性能差异可能会受到其他代码的影响(虽然我认为这是不可能的,但仍然值得尝试)。所以我提取了所有必要的代码(自己使用的实现),并将它们放在我手动生成的VS2010项目中。令人惊讶但通常情况下,我发现全局的一个比成员一个稍快,这与@black @Myles Hathcock的结果相同,即使我保持代码的实现不变。
第3 强>
因为在我们的项目中,outerProduct
被放入发布的lib文件中,而在我的手动生成项目中,它直接生成.obj文件,并链接到.exe文件。为了排除这个问题,我使用提取的代码并通过VS2010生成lib文件,并将此lib文件应用于另一个VS项目以测试性能差异,但是全局的文件仍然比成员的快一点。因此,两个代码具有相同的实现,并且它们都被放入lib文件中,尽管一个是由Scons生成的,另一个是由VS项目生成的,但它们具有不同的性能。 Scons会导致这个问题吗?
<强> 4 强>
对于我的问题中显示的代码,全局函数outerProduct
在.h
文件中声明和定义,然后#include
由.cpp
文件声明。因此,在编译此.cpp
文件时,outerProduct
将被实例化。但是,如果我将其更改为另一种方式:
(我必须提醒这些代码现在由 Scons 编译到产品lib
文件,而不是手动生成的VS2010项目)<登记/>
首先,我在outerProduct
文件中声明全局函数.h
:
\\outProduct.h
template<typename Scalar, int Dim>
void outerProduct(const Vector<Scalar, Dim> & v1 , const Vector<Scalar, Dim> & v2, SquareMatrix<Scalar, Dim> & m);
然后在.cpp
文件中,
\\outerProduct.cpp
template<typename Scalar, int Dim>
void outerProduct(const Vector<Scalar, Dim> & v1 , const Vector<Scalar, Dim> & v2, SquareMatrix<Scalar, Dim> & m)
{
for (unsigned int i=0; i<Dim; i++)
for (unsigned int j=0; j<Dim; j++)
{
m(i,j) = v1[i]*v2[j];
}
}
由于它是一个模板函数,它需要一些显式的实例化:
\\outerProduct.cpp
template void outerProduct<double, 3>(const Vector<double, 3> &, const Vector<double, 3> &, SquareMatrix<double, 3> &);
template void outerProduct<float, 3>(const Vector<float, 3> &, const Vector<float, 3> &, SquareMatrix<float, 3> &);
最后,在调用此函数的.cpp
文件中:
\\use_outerProduct.cpp
#include "outerProduct.h" //note: outerProduct.cpp is not needful.
...
outerProduct(v1, v2, m)
...
现在,奇怪的是,全球最终会比第一个成员快一点,如下图所示:
但这只发生在Scons环境中。在手动生成的VS2010项目中,全局项目总是比成员项目略快。那么这种性能差异只能来自Scons环境?如果模板函数被显式实例化,它会变得正常吗?
事情仍然很奇怪! 似乎Scons会做一些我没想到的事情。
// --------------------------------------------- ---------------------------
此外,测试代码现在更改为以下内容以避免循环省略:
Vector<double, 3> vec1(0.0);
Vector<double, 3> vec2(1.0);
Timer timer;
while(true)
{
timer.startTimer();
for (unsigned int i=0; i<100000; i++)
{
vec1 = Vector<double, 3>(i);
SquareMatrix<double,3> m = vec1.outerProduct(vec2);
}
timer.stopTimer();
cout<<"time cost for member function: "<< timer.getElapsedTime()<<endl;
timer.startTimer();
SquareMatrix<double,3> m;
for (unsigned int i=0; i<100000; i++)
{
vec1 = Vector<double, 3>(i);
outerProduct(vec1, vec2, m);
}
timer.stopTimer();
cout<<"time cost for global function: "<< timer.getElapsedTime()<<endl;
system("pause");
}
@black @Myles Hathcock,非常感谢热情的人们! Vector
和SquareMatrix
在Physika_Src/Physika_Core
文件夹中定义!但是没有上传全局函数outerProduct
,您可以将其适当地添加到某个地方。