导语
最近总结了一些可以提高C++执行性能的代码设计案例,觉得挺有意思,在这里分享给大家。此处不讨论算法复杂度对执行性能的影响,而是假定算法足够好,或者算法已经没法继续优化下去了,通过优化C++写法来提高执行性能。
本文的性能
狭义地解释为性能中的时间效率。
面向对象时意料之外的开销
1. 构造函数和析构函数的惊人开销
比如笔者写了个咸鱼日志类(仅用于讨论问题,实际生产环境不会这么实现日志类),定义了日志级别DEBUG、INFO、WARNING、ERROR,
struct log_level {
enum type {
DEBUG = 1,
INFO = 2,
WARNING = 3,
ERROR = 4
};
};
每个级别都有一个xx_log函数来打日志,会根据设置的级别判断是否输出日志,日志内容是函数名和出入的日志文本。
inline void debug_log(const std::string& func_name, const std::string& log_message) {
if (allow_log_level_ <= log_level::DEBUG) {
std::cout << "DEBUG|" << func_name << ": " << log_message << "\n";
}
}
inline void error_log(const std::string& func_name, const std::string& log_message) {
if (allow_log_level_ <= log_level::ERROR) {
std::cout << "ERROR|" << func_name << ": " << log_message << "\n";
}
}
在构造类对象的时候从进程配置里读取可输出的日志级别,比如配置了ERROR级别。
auto log_handle = std::make_shared<my_log>(get_log_level_from_config());
然后for循环1亿次,所以i ++了1亿次,并且calc1 ++了1亿次,以及调了1亿次debug_log,但是我们设置的ERROR级别,所以log不会打出来,所以我们可能认为debug_log不会产生多大开销。
但笔者用执行了1亿次除了不掉debug_log以为完全一样的代码,结果非常惊人。
请看完整代码
// Copyright 程序喵Plus
// gcc version 14.2.0 (GCC)
#include <iostream>
#include <memory>
#include <chrono>
class my_log {
public:
struct log_level {
enum type {
DEBUG = 1,
INFO = 2,
WARNING = 3,
ERROR = 4
};
};
my_log(log_level::type input_allow_log_level) : allow_log_level_(input_allow_log_level) {}
~my_log() {}
public:
inline void debug_log(const std::string& func_name, const std::string& log_message) {
if (allow_log_level_ <= log_level::DEBUG) {
std::cout << "DEBUG|" << func_name << ": " << log_message << "\n";
}
}
inline void error_log(const std::string& func_name, const std::string& log_message) {
if (allow_log_level_ <= log_level::ERROR) {
std::cout << "ERROR|" << func_name << ": " << log_message << "\n";
}
}
// 其它略
private:
log_level::type allow_log_level_;
};
inline my_log::log_level::type get_log_level_from_config() {
// balabala
// ....
return my_log::log_level::ERROR;
}
int main()
{
auto log_handle = std::make_shared<my_log>(get_log_level_from_config());
auto ms_start = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
);
int32_t calc1 = 0;
for (int32_t i = 0; i <= 100000000; i++) {
calc1++;
}
auto ms_step = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
);
int32_t calc2 = 0;
for (int32_t i = 0; i <= 100000000; i++) {
calc2++;
log_handle->debug_log("main", "debug一下");
}
auto ms_end = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
);
log_handle->error_log("main", "step1_cost: " + std::to_string(ms_step.count() - ms_start.count()) +
"ms, step2_cost:" + std::to_string(ms_end.count() - ms_step.count()) + "ms");
return 0;
}
输出的结果是
ERROR|main: case1_cost: 74ms
ERROR|main: case2_cost: 7371ms
在调debug_log但不打日志的情况竟然比不调debug_log,慢了92.5倍。
让我们重新看下这个咸鱼函数干了啥:
inline void debug_log(const std::string& func_name, const std::string& log_message) {
if (allow_log_level_ <= log_level::DEBUG) {
std::cout << "DEBUG|" << func_name << ": " << log_message << "\n";
}
}
// ......
log_handle->debug_log("main", "debug一下");
首先这里使用了建议编译器inline,并且函数足够短也没有循环或者迭代或者递归,所以这里忽略函数调用开销(编译的时候究竟有没有内联需要编译器大哥说了算,关于C++的内联机制这里不做讨论,之后再出文章讨论吧)。
那么仅仅是每次循环多调了2次构造函数和析构函数。
但考虑到毕竟函数里有个if判断语句,保险起见,笔者加个case,在我们构造好2个string对象,调用debug_log的时候不需要进行构造,如下
std::string mains = "main", debugs = "debug一下";
for (int32_t i = 0; i <= 100000000; i++) {
calc1++;
log_handle->debug_log(mains, debugs);
}
auto ms_end_plus = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
);
结果是
ERROR|main: case1_cost: 74ms
ERROR|main: case2_cost: 7371ms
ERROR|main: case3_cost: 626ms
case2比case3慢了11.7倍。它们唯一的差别是,case2额外多进行了,1亿次的:
-
传入"main"构造了一个string对象func_name -
传入"debug一下"构造了一个string对象log_message -
调用结束之后析构string对象log_message -
调用结束之后析构string对象func_name
由此可见只要调用的频率足够高,尤其是很多计算密集型场景,构造函数和析构函数的性能消耗比我们往常预期的要大的多,此时我们需要尽量避开大规模构造和析构对象。
2.剩下的内容下期再讨论了
剩下的内容,比如延迟构造、冗余构造、继承、虚函数在某些情况下产生的惊人的性能开销,下期再讨论了。笔者该睡了,明天还得上班呢。(文章定时明天早上8:00发布)
本博所有文章均为博主原创,未经许可不得转载。
https://www.prolightsfxjh.com/article/cpp-constructors-destructors/
Thank you!
------from ProLightsfx
如果你对推荐系统、游戏开发、C++优化等领域感兴趣或者想参与讨论的话,欢迎关注笔者公众号。
非特殊说明,本博所有文章均为博主原创,未经许可不得转载。
如经许可后转载,请注明出处:https://prolightsfxjh.com/article/cpp-constructors-destructors/
共有 0 条评论