Недавно я начал использовать 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.
Я понятия не имею, почему это становится медленнее. Есть ли у вас какие-либо идеи?
Постскриптум Ниже вы можете найти наиболее трудоемкую функцию:
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, замедляет программу?
Это ошибка в 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 »
Я попытаюсь решить эту проблему, но заранее заметьте, что весьма вероятно, что этот символ отвечает за выбор правильной оптимизированной версии на основе процессора, что может быть частью снижения производительности.