Я пытаюсь создать класс C ++, который ведет себя точно так же, как встроенный тип int
, с одним исключением: везде, где вызывается оператор * (или оператор * =), вместо него вызывается сложение.
ВВо-первых, производительность моего класса была очень плохой (1/2 от встроенного int
типа), но я заметил, что это потому, что я забыл включить конструктор копирования ниже:
struct AlmostInt {
AlmostInt () { }
AlmostInt (const AlmostInt &a) : val(a.val) { } // forgetting this killed
// performance
AlmostInt operator+(const AlmostInt &a) const { AlmostInt result = *this;
result.val += a.val;
return result; }
AlmostInt operator-(const AlmostInt &a) const { AlmostInt result = *this;
result.val -= a.val;
return result; }
AlmostInt operator*(const AlmostInt &a) const { AlmostInt result = *this;
result.val = result.val + a.val;
return result; }
AlmostInt &operator+=(const AlmostInt &a) { this->val += a.val;
return *this; }
AlmostInt &operator-=(const AlmostInt &a) { this->val -= a.val;
return *this; }
AlmostInt &operator*=(const AlmostInt &a) { this->val = this->val + a.val);
return *this; }
private:
int val;
};
К сожалению,моя программа остается на 25% медленнее, чем должна быть.Изучая сборку, созданную для двух разных версий программы (одна использует int
, другая - AlmostInt
), я вижу, что число идентичных операций + и - одинаковое, поэтому на каком-то уровне все «работает».
Проблема заключается в том, что в коде значительно больше операций загрузки и хранения с использованием класса AlmostInt
, а не собственной операции int
.
У кого-нибудь есть идеи относительно того, гдеэти накладные расходы могут быть из?Единственное предположение, которое у меня было, заключалось в том, что, возможно, компилятор не понимает, что AlmostInt
имеет все те же свойства, что и int
(например, ассоциативность, коммутативность), но если бы это было действительно проблемой, я бы ожидал другое числоИнструкции «+» или «-» в коде, и этого не происходит.
Я подозреваю, что дополнительные загрузки и хранилища связаны с дополнительной активностью стека, но все, что я могу сказать на данный момент, этоэто не просто несколько дополнительных загрузок стека и хранения в верхней и нижней части каждой функции, но дополнительные загрузки и сохранения происходят во всем коде.
Есть идеи?Интересно, кто-нибудь может указать мне на компилятор, который делает позволяющим достичь уровня производительности int
с пользовательским классом.
ОБНОВЛЕНИЕ:
Здесьэто простая функция, которую вы можете вырезать и вставить, чтобы увидеть, что происходит для вас.В x86-64 Linux (g ++ 4.3, 4.4), AIX6 xlC и нескольких других платформах изменение строки «CHOOSE ONE ...» ниже должно привести к тому же самому генерируемому коду (или по крайней мерекод той же производительности), но на практике код значительно увеличивается.Может кто-нибудь объяснить, что происходит (для какой-то конкретной платформы / компилятора) или как это исправить?
class AlmostInt
{
int value;
public:
AlmostInt& operator+=(AlmostInt that)
{
value += that.value;
return *this;
}
AlmostInt& operator-=(AlmostInt that)
{
value -= that.value;
return *this;
}
AlmostInt& operator*=(AlmostInt that)
{
value *= that.value;
return *this;
}
};
AlmostInt operator+(AlmostInt lhs, AlmostInt rhs)
{
lhs += rhs;
return lhs;
}
AlmostInt operator-(AlmostInt lhs, AlmostInt rhs)
{
lhs -= rhs;
return lhs;
}
AlmostInt operator*(AlmostInt lhs, AlmostInt rhs)
{
lhs *= rhs;
return lhs;
}
// CHOOSE ONE OF THE FOLLOWING TWO LINES:
//typedef int real;
typedef AlmostInt real;
typedef struct {
real re;
real im;
} complex;
#define R(a0,a1,b0,b1,wre,wim) { \
t1 = a0 - a1; t2 = b0 - b1; \
t5 = t1 * wim; t6 = t2 * wim; \
t3 = a0; t1 *= wre; \
t3 += a1; t2 *= wre; \
t1 -= t6; t4 = b0; \
t2 += t5; t4 += b1; \
a0 = t3; b1 = t2; \
a1 = t4; b0 = t1; \
}
#define RZERO(a0,a1,b0,b1) { \
t1 = a0 - a1; t2 = b0 - b1; \
t3 = a0 + a1; t4 = b0 + b1; \
b0 = t1; a0 = t3; \
b1 = t2; a1 = t4; \
}
void rpass(real *a, const complex *w, unsigned int n)
{
real t1, t2, t3, t4, t5, t6, t7, t8;
real *b;
unsigned int k;
b = a + 4 * n;
k = n - 2;
RZERO(a[0],a[1],b[0],b[1]);
R(a[2],a[3],b[2],b[3],w[0].re,w[0].im);
R(a[4],a[5],b[4],b[5],w[1].re,w[1].im);
R(a[6],a[7],b[6],b[7],w[2].re,w[2].im);
for (;;) {
R(a[8],a[9],b[8],b[9],w[3].re,w[3].im);
R(a[10],a[11],b[10],b[11],w[4].re,w[4].im);
R(a[12],a[13],b[12],b[13],w[5].re,w[5].im);
R(a[14],a[15],b[14],b[15],w[6].re,w[6].im);
if (!(k -= 2)) break;
a += 8;
b += 8;
w += 4;
}
}
(Кредит, где кредит должен: этот небольшой тест взят из библиотеки 'djbfft' Дэна Бернштейна)