这是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)?
答案 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”修改,看看它有什么不同。