关于函数指针:当函数内容发生变化时,为什么开销时间会发生变化

时间:2014-02-06 07:48:08

标签: c++ function-pointers

这是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)?

1 个答案:

答案 0 :(得分:0)

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

Tempfunction_time = 1.47983
499500
TempfunctionPtr_time = 1.69183
499500

现在,我得到的结果是GCC(版本4.6.3 - 其他版本可用,可能会给出其他结果!),这与Microsoft的编译器不同,因此结果可能不同 - 不同的编译器优化代码有时不同。我真的很惊讶编译器没有发现TempFunction的结果只需要计算一次。但是,嘿,没有诡计就更容易编写基准。

我的第二个观察是,使用我的编译器,如果我用主代码周围的循环int N=1000;替换for(int N=1000; N <= 8000; N *= 2),这两种情况之间没有或几乎没有差别 - 我不是完全确定原因,因为代码看起来相同(没有通过函数指针调用,因为编译器知道函数指针是常量),并且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;
    }
}

然而,差异是几千秒,而变体是一个明显的赢家,唯一的结论是明显的,“调用函数比内联慢”。

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”修改,看看它有什么不同。