【问题标题】:about function pointer: why the overhead time changes when the content of the function changes关于函数指针:为什么函数内容改变时开销时间会改变
【发布时间】:2014-02-06 07:48:08
【问题描述】:

这里是c++代码,我用的是vs2013,发布模式

#include <ctime>
#include <iostream>

void Tempfunction(double& a, int N)
{
    a = 0;
    for (double i = 0; i < N; ++i)
    {
    a += i;
    }
}

int main()
{
    int N = 1000; // from 1000 to 8000

    double Value = 0;
    auto t0 = std::time(0);
    for (int i = 0; i < 1000000; ++i)
    {
        Tempfunction(Value, N);
    }
    auto t1 = std::time(0);
    auto Tempfunction_time = t1-t0;
    std::cout << "Tempfunction_time = " << Tempfunction_time << '\n';

    auto TempfunctionPtr = &Tempfunction;

    Value = 0;
    t0 = std::time(0);
    for (int i = 0; i < 1000000; ++i)
    {
        (*TempfunctionPtr)(Value, N);
    }
    t1 = std::time(0);
    auto TempfunctionPtr_time = t1-t0;
    std::cout << "TempfunctionPtr_time = " << TempfunctionPtr_time << '\n';

    std::system("pause");
}

我将N的值从1000改为8000,并记录Tempfunction_time和TempfunctionPtr_time。 结果很奇怪:

N=1000 , Tempfunction_time=1, TempfunctionPtr_time=2;
N=2000 , Tempfunction_time=2, TempfunctionPtr_time=6;
N=4000 , Tempfunction_time=4, TempfunctionPtr_time=11;
N=8000 , Tempfunction_time=8, TempfunctionPtr_time=21;

TempfunctionPtr_time - Tempfunction_time 不是常数, 和 TempfunctionPtr_time = 2~3 * Tempfunction_time。 区别应该是一个常量,就是函数指针的开销。

怎么了?

编辑:

假设 VS2013 内联 Tempfunction,如果它是由 Tempfunction() 调用的,而如果它是由 (*TempfunctionPtr) 调用的,则不内联它,那么我们可以解释一下区别。那么,如果这是真的,为什么编译器不能内联 (*TempfunctionPtr) ?

【问题讨论】:

  • 我现在看到你在发布模式下构建它。我现在确定这是优化。关闭所有可能的优化(我不知道在 vs 2013 中如何做)并再次尝试查看。
  • std::time 以秒为单位,也许你需要毫秒精度的度量。
  • 实际上你可以使用 QueryPerformanceCounter,因为你在 Windows 上。
  • 使 'a' 和 'i' 易变并再次测试,也将 N 增加到 10000,这应该可以减轻 fptr 取消引用

标签: c++ function-pointers


【解决方案1】:

我在我的 Linux 机器上用 g++ 编译了现有代码,发现时间太短,无法以秒为单位准确测量,因此重写它以使用 std::chrono 更精确地测量时间 - 我也不得不“使用”变量Value(因此下面打印了“499500”),否则编译器将完全优化掉第一个循环。然后我得到以下结果:

Tempfunction_time = 1.47983
499500
TempfunctionPtr_time = 1.69183
499500

现在,我得到的结果是针对 GCC 的(版本 4.6.3 - 其他版本可用,可能会给出其他结果!),它与 Microsoft 的编译器不同,因此结果可能不同 - 不同的编译器相当优化代码有时不同。实际上,我很惊讶编译器没有发现TempFunction 的结果只需要计算一次。但是,嘿,让编写基准测试变得更容易,而无需任何技巧。

我的第二个观察结果是,使用我的编译器,如果我用循环 for(int N=1000; N &lt;= 8000; N *= 2) 围绕主代码替换int N=1000;,这两种情况之间没有或几乎没有区别 - 我不完全确定为什么,因为代码看起来相同(没有通过函数指针调用,因为编译器知道函数指针是一个常量),并且TempFUnction 在这两种情况下都被内联。 (当 N 是 1000 以外的其他值时,会发生相同的“平等” - 所以我不确定这里发生了什么......

要实际测量函数指针和直接函数调用之间的差异,您需要将TempFUnction 移动到单独的文件中,并“隐藏”存储在TempFunctionPtr 中的实际值,这样编译器就不会计算出确切地说出你在做什么。

最后,我得到了这样的结果:

typedef void (*FunPtr)(double &a, int N);

void Tempfunction(double& a, int N)
{
    a = 0;
    for (double i = 0; i < N; ++i)
    {
    a += i;
    }
}

FunPtr GetFunPtr()
{
    return &Tempfunction;
}

而“主要”代码是这样的:

#include <iostream>
#include <chrono>

typedef void (*FunPtr)(double &a, int N);

extern void Tempfunction(double& a, int N);
extern FunPtr GetFunPtr();

int main()
{
    for(int N = 1000; N <= 8000; N *= 2)
    {
    std::cout << "N=" << N << std::endl;
    double Value = 0;
    auto t0 = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000; ++i)
    {
        Tempfunction(Value, N);
    }
    auto t1 = std::chrono::system_clock::now();;
    std::chrono::duration<double> Tempfunction_time = t1-t0;
    std::cout << "Tempfunction_time = " << Tempfunction_time.count() << '\n';
    std::cout << Value << std::endl;

    auto TempfunctionPtr = GetFunPtr();

    Value = 0;
    t0 = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000; ++i)
    {
        (*TempfunctionPtr)(Value, N);
    }
    t1 = std::chrono::system_clock::now();
    std::chrono::duration<double> TempfunctionPtr_time = t1-t0;
    std::cout << "TempfunctionPtr_time = " << TempfunctionPtr_time.count() << '\n';
    std::cout << Value << std::endl;
    }
}

然而,相差几千秒,variant 显然是赢家,唯一的结论是显而易见的,“调用函数比内联函数慢”。

N=1000
Tempfunction_time = 1.78323
499500
TempfunctionPtr_time = 1.77822
499500
N=2000
Tempfunction_time = 3.54664
1.999e+06
TempfunctionPtr_time = 3.54687
1.999e+06
N=4000
Tempfunction_time = 7.0854
7.998e+06
TempfunctionPtr_time = 7.08706
7.998e+06
N=8000
Tempfunction_time = 14.1597
3.1996e+07
TempfunctionPtr_time = 14.1577
3.1996e+07

当然,如果我们“只做了一半的隐藏技巧”,使得函数在第一种情况下是已知的和可内联的,而不是通过函数指针是未知的,我们或许可以期待不同之处。但是通过指针调用函数本身并不昂贵。当编译器决定内联函数时,真正的区别就出现了。

显然,这些是 GCC 4.6.3 的结果,它与 MSVS2013 不是同一个编译器。您应该进行上述代码中的“chrono”修改,看看它有什么不同。

【讨论】:

  • 谢谢。我在 Windows 上得到了类似的结果。我认为 vs2013 为我发布的代码内联了 Tempfunction。而且,为什么内联可以提供非常量的性能提升?即,而不是 Tempfunction_time - TempfunctionPtr_time = 常数,我得到 Tempfunction_time = 2 ~ 3 * TempfunctionPtr_time?
  • 另一个问题:如果函数指针调用它,编译器真的不能内联函数吗? (例如,虚函数)
  • @liangbright 如果编译器可以证明方法没有被覆盖,它仍然可以内联。如今,编译器变得越来越聪明。然后现在甚至可以跨文件内联。
  • 在这种情况下可能 vs 没有内联,请阅读:stackoverflow.com/questions/16462800/…
  • 只要代码与内联代码一起正常工作,编译器就没有义务“不内联”函数。当然,不同的编译器在这里有不同程度的“聪明”。
猜你喜欢
  • 1970-01-01
  • 2018-02-24
  • 2015-10-14
  • 2013-10-29
  • 1970-01-01
  • 2018-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多