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

时间:2013-04-07 11:16:45

标签: c performance gcc

问题

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

我的项目详情

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

    #iter     C (fPIC)  C       C/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 -o f

代码:fractalMain.c

#include <time.h>
#include <stdio.h>
#include <stdbool.h>
#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 <stdio.h>
#include <stdbool.h>
#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 <= iter; ++i)
    {
        sqComplex(zPrev_re, zPrev_im, p_zNext_re, p_zNext_im);
        zNext_re = zNext_re + C_re;
        zNext_im = zNext_im + C_im;
        if(zNext_re*zNext_re+zNext_im*zNext_im > 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脚本的小c程序时(所以我做同样的事情,只是不需要.so) - 时间非常类似于C(没有-fPIC)。所以我在过去几天里通过一些配置检查了它,并且它始终显示两组非常相似的结果:没有-fPIC的速度更快,而且速度更慢。

3 个答案:

答案 0 :(得分:37)

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

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

如果编译时没有-fPIC,则可以进行相同的假设,即编译器可以安全地假设可执行文件中定义的全局符号不能插入,因为查找范围以可执行文件本身开头,然后遵循所有其他图书馆,包括预装的图书馆。

要获得更透彻的理解,请查看以下paper

答案 1 :(得分:3)

正如其他人已经指出的那样,-fPIC强制GCC禁用许多优化功能,例如内联和克隆。我想指出几种克服此问题的方法:

  • 如果要编译主要可执行文件(不是库),请用-fPIC替换-fPIE;由于安全原因,这是现代发行版中的默认版本
  • 使用-fvisibility=hidden__attribute__((visibility("default")))从库中仅导出必要的功能并隐藏其余功能;这将使GCC可以优化隐藏功能
  • 使用专用符号别名(__attribute__((alias ("__f")));)从库中引用库函数;这将再次解开海湾合作委员会的手
  • 先前的建议可以通过在最新的GCC版本中添加的-fno-semantic-interposition标志自动进行

答案 2 :(得分:1)

正如其他人在您的开篇帖子的评论部分中所讨论的那样,使用-flto进行编译应该有助于减少您在此特定情况下看到的运行时间差异,因为gcc的链接时间优化很可能弄清楚它实际上可以内联几个函数;)

通常,链接时优化可能会导致代码大小(~6%)link to paper on link time optimisations in gold大幅减少,因此运行时间也会缩短(更多程序适合缓存)。另请注意,-fPIC主要被视为一项功能,可实现更严格的安全性,并且always enabled in androidThis question on SO也简要讨论过。另外,为了让您知道,-fpic-fPIC的更快版本,因此如果您必须使用-fPIC,请尝试使用-fpic - link to gcc docs。对于x86,它可能没什么区别,但你需要自己检查/在gcc-help上询问。