重复间接执行
Performance of repetitive indirection
我发现自己在争论我是否想像代码 1 和代码 2 那样写。在我看来,代码 1 看起来更干净,但从理论上讲,与代码 2 相比,由于其额外的间接性,我可以预期性能损失吗? 这里有任何相关的编译器优化吗? 如果 bar() 返回 Bar*,有什么变化吗?
代码 1:
foo.bar().method1();
foo.bar().method2();
foo.bar().method3();
foo.bar().method4();
代码 2:
Bar& bar = foo.bar(); //Java programmers: ignore ampersand
bar.method1();
bar.method2();
bar.method3();
bar.method4();
编辑:我认为有太多的变量可以问这样一个一般性的问题(例如,常量与非常量方法,编译器是否内联方法,编译器如何处理引用等)。在汇编中分析我的特定代码可能是要走的路。
Code_2相比,Code_1似乎有性能损失。
但请记住鲁棒C++设计的最基本规则:- 过早的优化是万恶之源。为了清楚起见,首先制作代码,然后指定一个好的分析器作为您的"大师"。
第二种选择,Bar bar = foo.bar()
肯定更有效率,尽管多少取决于重量条的重量。差异很可能是微不足道的;尝试基准测试。
我认为第二个选项更具可读性,但这正在进入个人风格。我认为你真正想要的是一个在内部调用所有四个方法的method5
。因此,您可以拥有
foo.bar().method5();
仅此而已。
根据bar
实际执行的操作,可能会或可能不会(明显的)性能损失。一个更有趣的问题是,是什么让你认为你的第一种方法是"更干净"的。
在不知道实现的任何细节的情况下,我实际上倾向于相反的想法:后一种方法不仅更短(短是好的,因为更少的代码就是更少的错误,更少的内容),而且更干净,更具可读性。
它清楚地反映了作者的意图,并没有让读者想知道实施bar
的细节,这可能会导致意想不到的副作用,而这些副作用可能是有意的,也可能不是有意的和/或期望的。
不要这样做,除非你有很好的理由。
参考测试
我进行了一个简单的测试。 在没有优化的情况下编译时,在我的机器上Test_1需要 1272 毫秒,Test_2 1108 毫秒(我运行了几次测试,结果在几毫秒内)。 通过 O2/O3 优化,两个测试似乎花费的时间相同:946 毫秒。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <chrono>
using namespace std;
class Foo
{
public:
Foo() : x_(0) {}
void add(unsigned amt)
{
x_ += amt;
}
unsigned x_;
};
class Bar
{
public:
Foo& get()
{
return foo_;
}
private:
Foo foo_;
};
int main()
{
srand(time(NULL));
Bar bar;
constexpr int N = 100000000;
//Foo& foo = bar.get(); //TEST_2
auto start_time = chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i)
{
bar.get().add(rand()); //TEST_1
//foo.add(rand()); //TEST_2
}
auto end_time = chrono::high_resolution_clock::now();
cout << bar.get().x_ << endl;
cout << "Time: ";
cout << chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << endl;
}
指针测试
我重新运行了测试,但这次是类成员是一个指针。 在没有优化的情况下编译时,在我的机器上Test_3需要 1285-1340 毫秒,Test_4 1110 毫秒。 通过 O2/O3 优化,两个测试似乎花费的时间相同:915 毫秒(令人惊讶的是,比上面的参考测试时间短)。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <chrono>
using namespace std;
class Foo
{
public:
Foo() : x_(0) {}
void add(unsigned amt)
{
x_ += amt;
}
unsigned x_;
};
class Bar
{
public:
~Bar()
{
delete foo_;
}
Foo* get()
{
return foo_;
}
private:
Foo* foo_ = new Foo;
};
int main()
{
srand(time(NULL));
Bar bar;
constexpr int N = 100000000;
//Foo* foo = bar.get(); //TEST_4
auto start_time = chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i)
{
bar.get()->add(rand()); //TEST_3
//foo->add(rand()); //TEST_4
}
auto end_time = chrono::high_resolution_clock::now();
cout << bar.get()->x_ << endl;
cout << "C++ Time: ";
cout << chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << endl;
}
结论
根据我机器上的这些简单测试,当未启用优化时,Code 2 样式的速度略快约 ~15%,但启用优化后,性能没有差异。
在大多数情况下,这两种选择都不是一个好的选择。两者都通过引用公开内部结构,从而破坏封装。我认为Foo对象的抽象级别太低,无法使用它,它应该提供更多类似服务的功能来使用它。
而不是
Bar& bar = foo.bar(); //Java programmers: ignore ampersand
bar.method1();
bar.method2();
bar.method3();
bar.method4();
Foo中应该有一些更高级的方法
class Foo
{
public:
void doSomething()
{
Bar& bar = foo.bar(); //Java programmers: ignore ampersand
bar.method1();
bar.method2();
bar.method3();
bar.method4();
}
private:
Bar& bar();
};
切勿向客户端提供对内部的非常量引用。
- 在执行其他功能的同时播放动画(LED矩阵和Arduino/ESP8266)
- C++,系统无法执行指定的程序
- 使用C++中的模板和运算符重载执行矩阵运算
- 创建一个函数以在输入为负数或零时输出字符串.第一次执行用户定义的函数
- 执行函数时导致崩溃的变量
- 无论条件是否为true,if总是在c++中执行
- 当函数模板参数是具有默认参数的类模板时,函数模板参数的推导如何执行
- 在C++中对T*类型执行std::move的意外行为
- 使用QProcess执行命令,并将结果存储在QStringList中
- 如何在没有信号的情况下从C++执行QML插槽
- 如何确认我的constexpr表达式实际上已经在编译时执行
- C++17中的并行执行策略
- QML按钮点击功能执行顺序
- 程序在执行程序的其余部分之前退出
- 为什么catch中的代码没有被执行
- C++从其他 constexpr 创建 lambda 不能按顺序执行 Constexpr
- 将执行、作业和WinAPI相乘
- 对字符数组中的元素执行逐位操作
- 为什么g++在未执行的代码处标记强制转换错误
- 如何使用原子指针执行双缓冲