我目前正在开发一个程序,我的目标是使用尽可能少的存储空间,尽可能快地在(一个类的(两个对象之间进行大量操作。
class number
{
public:
number(int x) :x{x} {};
int x;
// option 1
number operator+(number x)
{
return number(this->x + x.x);
};
// option 2
static void add(number* a, number* b, number* dest)
{
dest->x = a->x + b->x;
};
};
int main()
{
number a(2);
number b(2);
number c(0);
// 4,608e-8 sec
c = a + b;
// 2,318e-8 sec
number::add(&a,&b,&c);
}
我考虑了两种选择:
- 使用实际运算符
- 使用一个静态函数,将三个变量(包括目的地(作为参数
第一个可读性最好,但大规模使用它可能意味着需要大量空间,因为每次运行都会初始化一个新对象。
我可能已经用选项2解决了这个问题。通过获取指向目的地的指针,从而重用存储空间。选项2读起来有点笨重,如果有更多的操作相继发生,代码可能很难理解。
我已经进行了几次速度和空间测试。使用实际的操作员功能每次运行需要4,6e-8秒,存储量为920kb。空隙大小分别为2,3e-8和915kb。
我有什么选择不见了吗?如果不是,在存储空间、速度和可读性之间,哪一个是更好的折衷?
;正确的";担心性能的方法是启用编译器优化。大多数时候就是这样。
编写正确可读的代码。一旦你有了它,你就可以衡量它。如果你发现一段代码很贵,你可以看看生成的程序集,了解它为什么很贵。
将两个整数相加,最简洁的是:
int main() {
return 2+2;
}
编译器输出(带有-O3的gcc 9.2(:
main:
mov eax, 4
ret
现在,您可能想要将整数封装到一个类中:
struct number {
int x;
};
int main() {
return number{2}.x+number{2}.x;
}
这增加了创建类实例和访问其成员的成本。编译器输出:
main:
mov eax, 4
ret
您可以使用operator+
:,而不是直接使用内置的+
struct number {
int x;
number operator+(const number& other){
return {x+ other.x};
}
};
int main() {
return (number{2}+number{2}).x;
}
这增加了呼叫运营商的成本。编译器输出:
main:
mov eax, 4
ret
你的静态方法版本(修改最少(是这样的:
struct number {
int x;
static void add(number* a, number* b, number* dest) {
dest->x = a->x + b->x;
};
};
int main()
{
number a{2};
number b{2};
number c{0};
number::add(&a,&b,&c);
return c.x;
}
这增加了呼叫者必须"呼叫"的成本;准备";返回值,因为它是一个outparameter。它通过指针增加了间接寻址的成本。它增加了使用使类膨胀的static
方法的成本。编译器输出:
main:
mov eax, 4
ret
结论:不要过早地进行优化。在上述情况下,您所支付的所有成本都在您的代码中,对生成的程序集没有影响。编写可读且简单的代码,并将mirco优化留给编译器。如果您已经编写了正确的代码,并对其进行了测量,然后意识到存在瓶颈,那么您当然可以尝试改进该部分。不过,在做这些之前,尝试为最通用的用例改进number
是徒劳的。编译器在这方面做得更好。
PS关于你的数字(4608e-8秒vs 2318e-8秒钟(,我不得不承认我忽略了它们。e-8秒太小,意义不大。同样,对于基准测试,你需要分享基准测试的确切代码,你使用的编译器的详细信息和选项,以及你在什么硬件上运行它。坦率地说,没有这些细节,数字就没有意义。