Рассмотрим это struct
, которое может, например, представлять структуру из двух векторов 4D:
struct A {
double x[4];
double y[4];
A() : A(0.0, 0.0) { }
A(double xp, double yp)
{
std::fill_n(x, 4, xp);
std::fill_n(y, 4, yp);
}
// Simple element-wise delegation of the mathematical operations
friend A operator+(const A &l, const A &r)
{
A res;
for (int i = 0; i < 4; i++)
{
res.x[i] = l.x[i] + r.x[i];
res.y[i] = l.y[i] + r.y[i];
}
return res;
}
friend A operator*(const A &l, const double &r)
{
A res;
for (int i = 0; i < 4; i++)
{
res.x[i] = l.x[i] * r;
res.y[i] = l.y[i] * r;
}
return res;
}
friend A operator*(const double &l, const A &r)
{
A res;
for (int i = 0; i < 4; i++)
{
res.x[i] = l * r.x[i];
res.y[i] = l * r.y[i];
}
return res;
}
friend std::ostream &operator<<(std::ostream &stream, const A &a)
{
for (int i = 0; i < 4; i++)
std::cout << "(" << a.x[i] << "|" << a.y[i] << ") ";
return stream;
}
};
Для удобства struct
имеет несколько определенных операторов, которые просто делегируют элементу-элементупошаговые операции.
Теперь рассмотрим две разные версии второй struct B
, которая содержит объекты A
:
struct B { // version 1
double f1;
double f2; // Two coefficients
A buff1;
A buff2;
A buffa[4]; // Objects of struct A
// The following functions use the operators defined on struct A
void mathA(int i, double d) // Some math operations
{
buff2 = buff1 + buffa[i] * d;
}
void mathB() // Some more math (vector) operations
{
buff1 = f1 * (buffa[0] + buffa[3]) + f2 * (buffa[1] + buffa[2]);
}
};
и
struct B { // version 2
double f1;
double f2; // Two coefficients
A buff1;
A buff2;
A buffa[4]; // Objects of struct A
// The following functions DO NOT use the operators defined on struct A
void mathA(int i, double d) // Some math operations
{
for (int j = 0; j < 4; j++)
{
buff2.x[j] = buff1.x[j] + buffa[i].x[j] * d;
buff2.y[j] = buff1.y[j] + buffa[i].y[j] * d;
}
}
void mathB() // Some more math (vector) operations
{
for (int j = 0; j < 4; j++)
{
buff1.x[j] = f1 * (buffa[0].x[j] + buffa[3].x[j]) + f2 * (buffa[1].x[j] + buffa[2].x[j]);
buff1.y[j] = f1 * (buffa[0].y[j] + buffa[3].y[j]) + f2 * (buffa[1].y[j] + buffa[2].y[j]);
}
}
};
Как видите, вторая версия struct B
выполняет те же математические операции, но первая версия использует операторы struct A
, а вторая выполняет эти операции вручную в mathA
и mathB
. Обратите внимание, что вторая версия struct B
на самом деле не использует операторы, определенные в struct A
.
Давайте добавим основную функцию для проверки функциональности struct B
(окно различий, «Слева»):
int main(int argc, char **argv)
{
B b;
b.f1 = 0.5;
b.f2 = 0.8;
b.buff1 = A(0.7, 0.8);
b.buff2 = A(1.7, 2.8);
b.mathA(1, 0.9);
b.mathB();
std::cout << b.buff1 << "\n" << b.buff2;
}
Я подготовил примеры обоих случаев в Godbolt здесь . Оба случая скомпилированы с использованием g ++ 7.1.0 на уровне оптимизации -O3
. Левый регистр соответствует версии 1, правый регистр - версии 2 struct B
.
. Как видно из разборки, компилятор генерирует две метки для версии 1, которые соответствуют функциям mathX
. в struct B
:
64 B::mathA(int, double):
[…]
76 B::mathB():
Как показывает мой анализ, первый пример гораздо медленнее по сравнениюко второму примеру. В моем реальном коде функции вызываются более 1 миллиарда раз и, таким образом, вносят большой вклад в общее время выполнения. Я предполагаю, что это частично связано с переходами к определениям функций.
Есть ли способ заставить компилятор создать сборку, идентичную второму примеру? Т.е. с использованием определений операторов?
Обновление
Поскольку компилятор, казалось, генерировал метки и переходы для mathX(…)
, моя идея состояла в том, чтобы попытаться встроить эти функции. Использование ключевого слова inline
ничего не изменило, но для g ++ вы можете использовать __attribute__((always_inline))
, что заставит компилятор встроить функцию ( документация ):
struct B { // version 3
// …
mathA(int i, double d) __attribute__((always_inline))
{
// …
}
mathB() __attribute__((always_inline))
{
// …
}
};
Это улучшило производительность, который сейчас находится где-то между версией 1 и версией 2. Это все еще не идеально, но если не будет найдено лучшего решения, я пойду с этим.