36

问题

我正在测试一个计算 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 -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;
}

代码:分形.h

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

代码:分形.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 非常相似(没有 .so -fPIC)。所以我在过去几天里检查了它的一些配置,它始终显示两组非常相似的结果:更快没有-fPIC并且慢一点。

4

3 回答 3

51

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

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

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

如需更深入的了解,请查看以下论文

于 2013-04-08T22:46:51.573 回答
11

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

  • 如果您正在编译主要可执行文件(而不是库),请替换-fPIC为,-fPIE因为这允许编译器假定不可能进行插入;
  • 使用-fvisibility=hidden__attribute__((visibility("default")))从库中仅导出必要的功能并隐藏其余功能;这将允许 GCC 更积极地优化隐藏函数;
  • 使用私有符号别名 ( __attribute__((alias ("__f")));) 从库中引用库函数;这将再次解开 GCC 的手
  • 可以使用-fno-semantic-interposition最近 GCC 版本中添加的标志自动执行先前的建议

有趣的是,Clang 与 GCC 不同,因为它默认允许所有优化,而不管-fPIC(可以被覆盖-fsemantic-interposition以获得类似 GCC 的行为)。

于 2018-07-31T10:21:00.983 回答
2

正如其他人在您的开篇文章的评论部分所讨论的那样,编译-flto应该有助于减少您在这种特殊情况下看到的运行时间差异,因为 gcc 的链接时间优化可能会发现实际上可以内联几个功能;)

一般来说,链接时间优化可能会导致代码大小(~6%)的大量减少,链接到黄金链接时间优化的论文,因此也可以运行时间(更多的程序适合缓存)。另请注意,这-fPIC主要被视为一项可实现更严格安全性的功能,并且始终在 android 中启用SO上的这个问题也简要讨论了。另外,只是为了让您知道,-fpic是更快的版本-fPIC,所以如果您必须使用-fPICtry-fpic代替 -链接到 gcc 文档。对于 x86,它可能没有什么不同,但您需要自己检查/在 gcc-help 上询问。

于 2016-03-30T23:01:59.927 回答