gpt4 book ai didi

c++ - 自定义 C++ 类可以复制内置类型的性能吗?

转载 作者:IT老高 更新时间:2023-10-28 22:41:41 28 4
gpt4 key购买 nike

我正在尝试创建一个行为与内置 int 类型完全相同的 C++ 类,但有一个异常(exception):在调用 operator*(或 operator*=)的任何地方,都调用了加法。

起初,我的类的性能很差(是内置 int 类型的 1/2),但我注意到这是因为我忘记包含下面的复制构造函数:

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;
}
}

(信用到期:这个小基准来自 Dan Bernstein 的 'djbfft' 库)

最佳答案

在这类情况下,性能下降的最常见原因之一从函数返回值。理论上,编译器应该是能够对此进行优化,并执行与返回 int 相同的操作(前提是所有相关函数都是内联的);在实践中,所有我知道的编译器会在寄存器中返回一个 int ,但是对于一个类类型,将传递一个带有地址的附加隐藏参数临时的,并在此地址返回内存中的值。原因是像复制构造函数或赋值这样的东西需要一个地址(this 指针,对正在复制的内容的引用),以及编译器似乎没有认识到,一旦它内联了所有功能,地址将不再是必需的。 (还有事实上二进制 API 说要这样做,但是二进制 API通常只涉及结构,而不涉及非平凡的类型构造函数、析构函数和赋值运算符。)

关于c++ - 自定义 C++ 类可以复制内置类型的性能吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6328290/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com