发布版本中成员函数与全局函数之间的性能差异

时间:2015-05-24 10:57:25

标签: c++ performance global member-functions

我已经实现了两个函数来执行两个向量(不是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");

并捕获结果:
enter image description here

你可以看到成员功能几乎是全球功能的两倍。

另外,我的项目是基于64位Windows系统构建的,实际上代码用于生成基于Scons构造工具的静态lib文件,以及生成的vs2010项目文件。

我必须提醒奇怪的性能差异只发生在发布版本中,而在调试版本类型中,全局函数几乎是成员版本的五倍。(约0.10s vs 0.02s)

3 个答案:

答案 0 :(得分:3)

一种可能的解释:

使用内联,在第一种情况下,编译器可能知道result(i, j)(来自局部变量)不是别名this[i]vec3[j],因此标量数组都不是thisvec3的修改。

在第二种情况下,从功能的角度来看,变量可能是别名,因此每次写入m都可能会修改v1v2的标量,因此v1[i]都不会1}}也可以缓存v2[j]

您可以尝试使用restrict关键字扩展来检查我的假设是否正确。

答案 1 :(得分:2)

编辑:原始程序集中的循环省略已得到纠正

  

[释义]为什么成员函数和静态函数之间的性能不同?

我将从您提出的问题中提到的最简单的事情开始,逐步进行性能测试/分析的细微差别。

测量调试版本的性能是一个坏主意。编译器在许多地方都采取自由,例如将未初始化的数组归零,生成并非严格必要的额外代码,并且(显然)不经过诸如常数传播之类的微不足道的优化。这导致了下一点......

始终关注程序集。当涉及到性能的微妙之处时,C和C ++是高级语言。许多人甚至认为x86汇编是一种高级语言,因为每个指令在解码期间被分解成可能的几个微操作。你只能通过查看C ++代码来判断计算机在做什么。例如,根据您实现SquareMatrix的方式,编译器可能会也可能无法在优化期间执行复制省略。

在测试性能时输入一些更细微的主题...

确保编译器实际上正在生成循环。使用示例测试代码,g ++ 4.7.2实际上并不生成我的SquareMatrixVector实现的循环。我实现了它们以将所有组件初始化为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

总之:在判断系统的实现是否不同之前,您可能需要稍微加强代码。确保实际生成循环(查看程序集)并查看编译器是否能够从成员函数中删除返回值。

如果这些事情属实,您仍然会看到差异,那么您可以在此处发布SquareMatrixVector的实施方式,以便我们为您提供更多信息吗?

我的工作示例的完整代码,生成文件和生成的程序集是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)
...

现在,奇怪的是,全球最终会比第一个成员快一点,如下图所示:

enter image description here

但这只发生在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,非常感谢热情的人们!
@Myles Hathcock,你的解释真的是一个微妙而深奥的解释,但我想我会从中受益匪浅。
最后,整个实施工作正在进行中 https://github.com/FeiZhu/Physika
这是我们正在开发的物理引擎,您可以从中找到更多信息,包括整个源代码。 VectorSquareMatrixPhysika_Src/Physika_Core文件夹中定义!但是没有上传全局函数outerProduct,您可以将其适当地添加到某个地方。