Подтвердить что ты не робот

Сколько накладных расходов может добавить флаг -fPIC?

Вопрос

Я тестирую простой код, который вычисляет фрактал Мандельброта. Я проверял его производительность в зависимости от количества итераций в функции, которая проверяет, принадлежит ли точка множеству Мандельброта или нет. Удивительно то, что после добавления флага -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, который вызывает функцию isMandelbrot из C. Поэтому я создал общий объект для вызова его из lua - и имел большие временные различия. Но не мог понять, почему они росли с числом итераций. В итоге выяснилось, что это из-за -fPIC. Когда я создаю небольшую программу на c, которая вызывает мой скрипт lua (настолько эффективно, что я делаю то же самое, только не нужно .so) - времена очень похожи на C (без -fPIC). Поэтому я проверил его в нескольких конфигурациях за последние несколько дней, и он последовательно показывает два набора очень похожих результатов: быстрее без -fPIC и медленнее с ним.

4b9b3361

Ответ 1

Оказывается, когда вы компилируете без опции -fPIC multiplyComplex, sqComplex, isInSet и isMandelbrot автоматически создаются компилятором. Если вы определяете эти функции как статические, вы, скорее всего, получите такую ​​же производительность при компиляции с помощью -fPIC, потому что компилятор будет иметь возможность выполнять inlining.

Причина, по которой компилятор не может автоматически встроить вспомогательные функции, связан с интерполяцией символов. Независимый от положения код необходим для доступа ко всем глобальным данным косвенно, т.е. Через глобальную таблицу смещения. То же самое ограничение применяется к вызовам функций, которые должны пройти через таблицу привязки процедуры. Поскольку символ может быть вставлен другим во время выполнения (см. LD_PRELOAD), компилятор не может просто предположить, что безопасно встроить функцию с глобальной видимостью.

То же самое предположение может быть сделано, если вы компилируете без -fPIC, то есть компилятор может с уверенностью предположить, что глобальный символ, определенный в исполняемом файле, не может быть вставлен, поскольку область поиска начинается с самого исполняемого файла, за которым затем следуют все другие библиотеки, включая предварительно загруженные.

Для более глубокого понимания рассмотрим следующую статью.

Ответ 2

Как уже отмечали другие люди, -fPIC заставляет GCC отключить многие оптимизации, например, встраивание и клонирование. Я хотел бы указать на несколько способов преодолеть это:

  • замените -fPIC на -fPIE если вы компилируете основной исполняемый файл (не библиотеки); это по умолчанию на современных дистрибутивах по соображениям безопасности
  • используйте -fvisibility=hidden и __attribute__((visibility("default"))) чтобы экспортировать только необходимые функции из библиотеки и скрыть остальные; это позволило бы GCC оптимизировать скрытые функции
  • использовать псевдонимы закрытых символов (__attribute__((alias ("__f")));) для ссылки на библиотечные функции из библиотеки; это снова развяжет руки GCC
  • предыдущее предложение может быть автоматизировано с помощью -fno-semantic-interposition который был добавлен в последние версии GCC

Интересно отметить, что Clang отличается от GCC, так как он позволяет все оптимизации по умолчанию независимо от -fPIC (может быть переопределен с -fsemantic-interposition для получения GCC-подобного поведения).

Ответ 3

Как уже обсуждалось в разделе комментариев вашего вступительного сообщения, компиляция с -flto должна помочь уменьшить разницу во времени выполнения, которое вы видите для этого конкретного случая, поскольку оптимизация времени ссылки gcc, скорее всего, что на самом деле нормально встраивать несколько функций;)

В целом оптимизация времени ссылки может привести к массивному сокращению размера кода (~ 6%) ссылка на бумага по оптимизации времени соединения в золоте, и, таким образом, время выполнения (больше вашей программы подходит в кеше). Также обратите внимание, что -fPIC в основном рассматривается как функция, которая обеспечивает более надежную защиту и всегда включено в android. Этот вопрос на SO вкратце обсуждается. Кроме того, просто чтобы сообщить, -fPIC - это более быстрая версия -fPIC, поэтому, если вы должны использовать -fPIC try -fPIC вместо этого - ссылка в gcc docs. Для x86 это может не повлиять, но вам нужно проверить это для себя/попросить gcc-help.