Программа работает в 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.

Я понятия не имею, почему это становится медленнее. Есть ли у вас какие-либо идеи?

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

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, замедляет программу?

2 ответа

Решение

Это ошибка в 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 во время выполнения программы. Опять же, больше деталей в вышеупомянутых отчетах об ошибках.

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

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

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

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

Теперь есть зависимость от sinИтак, посмотрим, что изменилось. И проблема в том, чтобы сначала определить, какую платформу вы используете... В glibc есть 17 различных подпапок. sysdeps (где грех определен), поэтому я пошел за 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 байтов; То же самое для Синко. Не уверен, что это будет иметь огромное значение.

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

РЕДАКТИРОВАТЬ: Я попытался скомпилировать его с 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 »

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

Другие вопросы по тегам