优雅快速的运算符函数cpp
elegant and fast operator function cpp
我目前正在开发一个程序,我的目标是使用尽可能少的存储量 space 尽可能快地在两个对象(一个 class)之间进行大量操作.
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);
}
我考虑过两种选择:
- 使用实际操作员
- 使用将三个变量(包括目标)作为参数的静态函数
第一个可读性最好,但大规模使用它可能意味着需要大量 space,因为每隔 运行 就会初始化一个新对象。
我可能已经用选项 2 解决了这个问题。通过接受一个指向目的地的指针,从而重用存储 space。选项 2 读起来有点笨重,如果有更多的操作发生在另一个之后,代码可能很难理解。
我已经 运行 进行了一些速度和 space 测试。
使用实际的运算符函数每个 运行 槽和 920kb 存储需要 4,6e-8 秒。 void占用2,3e-8和915kb.
有没有我遗漏的选项?如果不是,哪一个是存储 space、速度和可读性之间更好的权衡?
担心性能的“正确”方法是打开编译器优化。大多数时候就是这样。
编写正确且可读的代码。一旦你有了它,你就可以测量它。如果您发现一段代码很昂贵,您可以查看生成的程序集以了解它为什么很昂贵。
两个整数相加最简洁的是:
int main() {
return 2+2;
}
Compiler output (gcc 9.2 with -O3):
main:
mov eax, 4
ret
现在您可能希望将整数包装成 class:
struct number {
int x;
};
int main() {
return number{2}.x+number{2}.x;
}
这增加了创建 class 实例和访问其成员的成本。编译器输出:
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;
}
这增加了调用者必须“准备”return 值的成本,因为它是一个外参数。它通过指针增加了间接成本。它增加了 static
方法的成本,使 class 膨胀。编译器输出:
main:
mov eax, 4
ret
结论:不要进行过早的优化。在上面,您支付的所有费用都在您的代码中,并且对生成的程序集没有影响。编写可读且简单的代码,并将微优化留给编译器。如果您编写了正确的代码并且对其进行了测量并且意识到存在瓶颈,那么您当然可以尝试改进该部分。但是,在执行任何操作之前,尝试针对最一般的用例改进 number
是徒劳的。编译器在这方面要好得多。
PS 关于您的数字(4,608e-8 秒对 2,318e-8 秒),我不得不承认我忽略了它们。 e-8 秒太少了,意义不大。此外,对于基准测试,您需要共享基准测试的确切代码、您使用的编译器和选项的详细信息,并说明您使用的是什么硬件 运行。坦率地说,如果没有这些细节,数字就毫无意义。
我目前正在开发一个程序,我的目标是使用尽可能少的存储量 space 尽可能快地在两个对象(一个 class)之间进行大量操作.
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);
}
我考虑过两种选择:
- 使用实际操作员
- 使用将三个变量(包括目标)作为参数的静态函数
第一个可读性最好,但大规模使用它可能意味着需要大量 space,因为每隔 运行 就会初始化一个新对象。
我可能已经用选项 2 解决了这个问题。通过接受一个指向目的地的指针,从而重用存储 space。选项 2 读起来有点笨重,如果有更多的操作发生在另一个之后,代码可能很难理解。
我已经 运行 进行了一些速度和 space 测试。 使用实际的运算符函数每个 运行 槽和 920kb 存储需要 4,6e-8 秒。 void占用2,3e-8和915kb.
有没有我遗漏的选项?如果不是,哪一个是存储 space、速度和可读性之间更好的权衡?
担心性能的“正确”方法是打开编译器优化。大多数时候就是这样。
编写正确且可读的代码。一旦你有了它,你就可以测量它。如果您发现一段代码很昂贵,您可以查看生成的程序集以了解它为什么很昂贵。
两个整数相加最简洁的是:
int main() {
return 2+2;
}
Compiler output (gcc 9.2 with -O3):
main:
mov eax, 4
ret
现在您可能希望将整数包装成 class:
struct number {
int x;
};
int main() {
return number{2}.x+number{2}.x;
}
这增加了创建 class 实例和访问其成员的成本。编译器输出:
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;
}
这增加了调用者必须“准备”return 值的成本,因为它是一个外参数。它通过指针增加了间接成本。它增加了 static
方法的成本,使 class 膨胀。编译器输出:
main:
mov eax, 4
ret
结论:不要进行过早的优化。在上面,您支付的所有费用都在您的代码中,并且对生成的程序集没有影响。编写可读且简单的代码,并将微优化留给编译器。如果您编写了正确的代码并且对其进行了测量并且意识到存在瓶颈,那么您当然可以尝试改进该部分。但是,在执行任何操作之前,尝试针对最一般的用例改进 number
是徒劳的。编译器在这方面要好得多。
PS 关于您的数字(4,608e-8 秒对 2,318e-8 秒),我不得不承认我忽略了它们。 e-8 秒太少了,意义不大。此外,对于基准测试,您需要共享基准测试的确切代码、您使用的编译器和选项的详细信息,并说明您使用的是什么硬件 运行。坦率地说,如果没有这些细节,数字就毫无意义。