-fPIC标志可以增加多少开销?

我正在测试一个计算Mandelbrot分形的简单代码。 我一直在检查它的性能,具体取决于函数中的迭代次数,它检查一个点是否属于Mandelbrot集。 令人惊讶的是,在添加-fPIC标志后,我的时间差异很大。 从我读到的开销通常可以忽略不计,我遇到的最高开销约为6%。 我大约30%。 任何建议将被认真考虑!

我的项目详情

我使用-O3标志,gcc 4.7.2,Ubuntu 12.04.2,x86_64。 结果如下

     #it​​er C(fPIC)CC / C(fPIC)
     1 0.01 0.01 1.00 
     100 0.04 0.03 0.75 
     200 0.06 0.04 0.67 
     500 0.15 0.1 0.67 
     1000 0.28 0.19 0.68
     2000 0.56 0.37 0.66 
     4000 1.11 0.72 0.65 
     8000 2.21 1.47 0.67
    16000 4.42 2.88 0.65 
    32000 8.8 5.77 0.66 
    64000 17.6 11.53 0.66

我使用的命令:

 gcc -O3 -fPIC fractalMain.c fractal.c -o ffpic gcc -O3 fractalMain.c fractal.c -of 

代码:fractalMain.c

 #include  #include  #include  #include "fractal.h" int main() { int iterNumber[] = {1, 100, 200, 500, 1000, 2000, 4000, 8000, 16000, 32000, 64000}; int it; for(it = 0; it < 11; ++it) { clock_t start = clock(); fractal(iterNumber[it]); clock_t end = clock(); double millis = (end - start)*1000 / CLOCKS_PER_SEC/(double)1000; printf("Iter: %d, time: %lf \n", iterNumber[it], millis); } return 0; } 

代码:fractal.h

 #ifndef FRACTAL_H #define FRACTAL_H void fractal(int iter); #endif 

代码:fractal.c

 #include  #include  #include "fractal.h" void multiplyComplex(double a_re, double a_im, double b_re, double b_im, double* res_re, double* res_im) { *res_re = a_re*b_re - a_im*b_im; *res_im = a_re*b_im + a_im*b_re; } void sqComplex(double a_re, double a_im, double* res_re, double* res_im) { multiplyComplex(a_re, a_im, a_re, a_im, res_re, res_im); } bool isInSet(double P_re, double P_im, double C_re, double C_im, int iter) { double zPrev_re = P_re; double zPrev_im = P_im; double zNext_re = 0; double zNext_im = 0; double* p_zNext_re = &zNext_re; double* p_zNext_im = &zNext_im; int i; for(i = 1; i  4) { return false; } zPrev_re = zNext_re; zPrev_im = zNext_im; } return true; } bool isMandelbrot(double P_re, double P_im, int iter) { return isInSet(0, 0, P_re, P_im, iter); } void fractal(int iter) { int noIterations = iter; double xMin = -1.8; double xMax = 1.6; double yMin = -1.3; double yMax = 0.8; int xDim = 512; int yDim = 384; double P_re, P_im; int nop; int x, y; for(x = 0; x < xDim; ++x) for(y = 0; y < yDim; ++y) { P_re = (double)x*(xMax-xMin)/(double)xDim+xMin; P_im = (double)y*(yMax-yMin)/(double)yDim+yMin; if(isMandelbrot(P_re, P_im, noIterations)) nop = x+y; } printf("%d", nop); } 

比较背后的故事

在构建可执行文件时(根据其中一条注释)添加-fPIC标志可能看起来有点人为。 所以说几句话:首先我只将程序编译为可执行程序,并希望与我的Lua代码进行比较,后者从C调用isMandelbrot函数。所以我创建了一个共享对象来从lua调用它 – 并且有很大的时间差异。 但无法理解他们为什么会在迭代次数上增长。 最终发现它是因为-fPIC 。 当我创建一个调用我的lua脚本的小程序时(我有效地做同样的事情,只需要.so) – 时间非常类似于C(没有-fPIC )。 所以我在过去的几天里通过一些配置检查了它,并且它始终显示两组非常相似的结果:没有-fPIC速度更快,而且速度更慢。

事实certificate,当你在没有-fPIC选项的情况下编译时, multiplyComplexsqComplexisInSetisMandelbrot会被编译器自动内联。 如果将这些函数定义为静态,则在使用-fPIC进行编译时可能会获得相同的性能,因为编译器可以自由执行内联。

编译器无法自动内联辅助函数的原因与符号插入有关。 需要位置无关代码来间接访问所有全局数据,即通过全局偏移表。 同样的约束适用于函数调用,函数调用必须通过过程链接表。 由于符号可能在运行时被另一个符号插入(请参阅LD_PRELOAD ),因此编译器不能简单地假设内联具有全局可见性的函数是安全的。

如果您在没有-fPIC情况下进行编译,则可以进行相同的假设,即编译器可以安全地假设可执行文件中定义的全局符号无法插入,因为查找范围以可执行文件本身开头,然后是所有其他库,包括预装的。

有关更透彻的了解,请查看以下文章 。

正如其他人已经指出的那样, -fPIC迫使GCC禁用许多优化,例如内联和克隆。 我想指出几种方法来克服这个问题:

  • use -fvisibility=hidden__attribute__((visibility("default")))只从库中导出必要的函数并隐藏其余的函数; 这将允许GCC优化隐藏function
  • 使用私有符号别名( __attribute__((alias ("__f"))); )来引用库中的库函数; 这将再次解开海湾合作委员会的手
  • 之前的建议可以使用最近GCC版本中添加的-fno-semantic-interposition标志自动执行

正如其他人在你的开篇文章的评论部分中所讨论的那样,使用-flto编译应该有助于减少你在这个特定情况下看到的运行时间的差异,因为gcc的链接时间优化很可能会发现它确实没问题内联几个函数;)

通常,链接时间优化可能导致代码大小(约6%) 链接到黄金链接时优化的纸张大量减少,因此运行时间(更多程序适合缓存)。 另请注意, -fPIC主要被视为一种function,可以实现更严格的安全性,并始终在Android中启用 。 关于SO的这个问题也简要讨论过。 另外,只是为了让你知道, -fpic-fPIC的更快版本,所以如果你必须使用-fPIC试试-fpic – 链接到gcc docs 。 对于x86,它可能没什么区别,但你需要自己检查/在gcc-help上询问。