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

Программа выполняется в 3 раза медленнее при компиляции с g++ 5.3.1, чем ту же программу, скомпилированную с g++ 4.8.4, ту же команду

Недавно я начал использовать Ubuntu 16.04 с g++ 5.3.1 и проверял, что моя программа работает в 3 раза медленнее. До этого я использовал Ubuntu 14.04, g++ 4.8.4. Я построил его с теми же командами: CFLAGS = -std=c++11 -Wall -O3.

Моя программа содержит циклы, заполненные математическими вызовами (sin, cos, exp). Здесь вы можете найти .

Я попытался скомпилировать с различными флагами оптимизации (O0, O1, O2, O3, Ofast), но во всех случаях проблема воспроизводится (с Ofast оба варианта выполняются быстрее, но первый работает в 3 раза медленнее).

В моей программе я использую libtinyxml-dev, libgslcblas. Но в обоих случаях они имеют одинаковые версии и не играют существенной роли в программе (согласно коду и профилированию callgrind) с точки зрения производительности.

Я выполнил профилирование, но это не дает мне представления о том, почему это происходит. Сравнение Kcachegrind (слева медленнее). Я только заметил, что теперь программа использует libm-2.23 по сравнению с libm-2.19 с Ubuntu 14.04.

Мой процессор i7-5820, Haswell.

Я понятия не имею, почему он становится медленнее. У вас есть идеи?

P.S. Ниже вы можете найти наиболее трудоемкую функцию:

void InclinedSum::prepare3D()
{
double buf1, buf2;
double sum_prev1 = 0.0, sum_prev2 = 0.0;
int break_idx1, break_idx2; 
int arr_idx;

for(int seg_idx = 0; seg_idx < props->K; seg_idx++)
{
    const Point& r = well->segs[seg_idx].r_bhp;

    for(int k = 0; k < props->K; k++)
    {
        arr_idx = seg_idx * props->K + k;
        F[arr_idx] = 0.0;

        break_idx2 = 0;

        for(int m = 1; m <= props->M; m++)
        {
            break_idx1 = 0;

            for(int l = 1; l <= props->L; l++)
            {
                buf1 = ((cos(M_PI * (double)(m) * well->segs[k].r1.x / props->sizes.x - M_PI * (double)(l) * well->segs[k].r1.z / props->sizes.z) -
                            cos(M_PI * (double)(m) * well->segs[k].r2.x / props->sizes.x - M_PI * (double)(l) * well->segs[k].r2.z / props->sizes.z)) /
                        ( M_PI * (double)(m) * tan(props->alpha) / props->sizes.x + M_PI * (double)(l) / props->sizes.z ) + 
                            (cos(M_PI * (double)(m) * well->segs[k].r1.x / props->sizes.x + M_PI * (double)(l) * well->segs[k].r1.z / props->sizes.z) -
                            cos(M_PI * (double)(m) * well->segs[k].r2.x / props->sizes.x + M_PI * (double)(l) * well->segs[k].r2.z / props->sizes.z)) /
                        ( M_PI * (double)(m) * tan(props->alpha) / props->sizes.x - M_PI * (double)(l) / props->sizes.z )
                            ) / 2.0;

                buf2 = sqrt((double)(m) * (double)(m) / props->sizes.x / props->sizes.x + (double)(l) * (double)(l) / props->sizes.z / props->sizes.z);

                for(int i = -props->I; i <= props->I; i++)
                {   

                    F[arr_idx] += buf1 / well->segs[k].length / buf2 *
                        ( exp(-M_PI * buf2 * fabs(r.y - props->r1.y + 2.0 * (double)(i) * props->sizes.y)) - 
                        exp(-M_PI * buf2 * fabs(r.y + props->r1.y + 2.0 * (double)(i) * props->sizes.y)) ) *
                        sin(M_PI * (double)(m) * r.x / props->sizes.x) * 
                        cos(M_PI * (double)(l) * r.z / props->sizes.z);
                }

                if( fabs(F[arr_idx] - sum_prev1) > F[arr_idx] * EQUALITY_TOLERANCE )
                {
                    sum_prev1 = F[arr_idx];
                    break_idx1 = 0;
                } else
                    break_idx1++;

                if(break_idx1 > 1)
                {
                    //std::cout << "l=" << l << std::endl;
                    break;
                }
            }

            if( fabs(F[arr_idx] - sum_prev2) > F[arr_idx] * EQUALITY_TOLERANCE )
            {
                sum_prev2 = F[arr_idx];
                break_idx2 = 0;
            } else
                break_idx2++;

            if(break_idx2 > 1)
            {
                std::cout << "m=" << m << std::endl;
                break;
            }
        }
    }
}
}

Дальнейшие исследования. Я написал следующую простую программу:

#include <cmath>
#include <iostream>
#include <chrono>

#define CYCLE_NUM 1E+7

using namespace std;
using namespace std::chrono;

int main()
{
    double sum = 0.0;

    auto t1 = high_resolution_clock::now();
    for(int i = 1; i < CYCLE_NUM; i++)
    {
        sum += sin((double)(i)) / (double)(i);
    }
    auto t2 = high_resolution_clock::now();

    microseconds::rep t = duration_cast<microseconds>(t2-t1).count();

    cout << "sum = " << sum << endl;
    cout << "time = " << (double)(t) / 1.E+6 << endl;

    return 0;
}

Мне действительно интересно, почему эта простая примерная программа 2,5 быстрее в g++ 4.8.4 libc-2.19 (libm-2.19), чем в g++ 5.3.1 libc-2.23 (libm-2.23).

Команда компиляции:

g++ -std=c++11 -O3 main.cpp -o sum

Использование других флагов оптимизации не меняет соотношение.

Как я могу понять, кто, gcc или libc, замедляет работу программы?

4b9b3361

Ответ 1

Это ошибка в glibc, которая затрагивает версии 2.23 (используется в Ubuntu 16.04) и в ранних версиях 2.24 (например, Fedora и Debian уже включают исправленные версии, которые больше не затрагиваются, Ubuntu 16.10 и 17.04 еще нет).

Замедление происходит от штрафа перехода регистра SSE на AVX. См. Отчет об ошибке glibc здесь: https://sourceware.org/bugzilla/show_bug.cgi?id=20495

Олег Стриков написал довольно обширный анализ в своем отчете об ошибке Ubuntu: https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/1663280

Без патча возможны различные возможные обходные пути: вы можете статически ставить свою проблему (т.е. добавить -static), или вы можете отключить ленивое связывание, установив переменную среды LD_BIND_NOW во время выполнения программы. Опять же, более подробно в приведенных выше сообщениях об ошибках.

Ответ 2

Для действительно точного ответа вам, вероятно, понадобится разработчик libm, чтобы посмотреть на ваш вопрос. Однако, вот мой пример - возьмите его как черновик, если я найду что-то еще, я добавлю его к этому ответу.

Сначала посмотрим на asm, сгенерированный GCC, между gcc 4.8.2 и gcc 5.3. Есть только 4 отличия:

  • в начале a xorpd преобразуется в pxor для тех же регистров
  • a pxor xmm1, xmm1 был добавлен до преобразования из int в double (cvtsi2sd)
  • a movsd был перемещен непосредственно перед конверсией
  • добавление (addsd) было перенесено непосредственно перед сравнением (ucomisd)

Все это, вероятно, недостаточно для снижения производительности. Наличие тонкого профилировщика (например, intel) может быть более убедительным, но у меня нет доступа к нему.

Теперь существует зависимость от sin, поэтому давайте посмотрим, что изменилось. И проблема в первую очередь определяет, какую платформу вы используете... Есть 17 разных подпапок в glibc sysdeps (где определяется sin), поэтому я пошел на x86_64 один.

Во-первых, обрабатываются возможности обработки процессора, например glibc/sysdeps/x86_64/fpu/multiarch/s_sin.c, используемые для проверки FMA/AVX в 2.19, но в 2.23 это делается извне. Там может быть ошибка, в которой возможности не сообщаются должным образом, в результате чего не используются FMA или AVX. Однако я не считаю эту гипотезу очень правдоподобной.

Во-вторых, в .../x86_64/fpu/s_sinf.S единственные изменения (кроме обновления авторских прав) изменяют смещение стека, выравнивая его до 16 байтов; idem для sincos. Не уверен, что это будет иметь огромное значение.

Однако в 2.23 добавлено множество источников для векторизованных версий математических функций, а некоторые используют AVX512 - что ваш процессор, вероятно, не поддерживает, потому что он действительно новый. Может быть, libm пытается использовать такие расширения, а так как у вас их нет, вернитесь к общей версии?

EDIT: Я попытался скомпилировать его с помощью gcc 4.8.5, но для этого мне нужно перекомпилировать glibc-2.19. На данный момент я не могу связать, из-за этого:

/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/libm.a(s_sin.o): in function « __cos »:
(.text+0x3542): undefined reference to « _dl_x86_cpu_features »
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/libm.a(s_sin.o): in function « __sin »:
(.text+0x3572): undefined reference to « _dl_x86_cpu_features »

Я попытаюсь разрешить это, но заранее заметьте, что очень вероятно, что этот символ отвечает за выбор правильной оптимизированной версии на основе процессора, которая может быть частью хита производительности.