笔记列表

《提高C++性能的编程技术》笔记:总结
《提高C++性能的编程技术》笔记:跟踪
《提高C++性能的编程技术》笔记:构造函数、析构函数
《提高C++性能的编程技术》笔记:临时对象
《提高C++性能的编程技术》笔记:内存池(单线程、多线程)
《提高C++性能的编程技术》笔记:内联
《提高C++性能的编程技术》笔记:STL
《提高C++性能的编程技术》笔记:引用计数
《提高C++性能的编程技术》笔记:编码优化
《提高C++性能的编程技术》笔记:设计优化/可扩展性/系统体系结构

对象的创建和销毁往往会造成性能的损失。在继承层次中,对象的创建将引起其先辈的创建。对象的销毁也是如此。其次,对象相关的开销与对象本身的派生链的长度和复杂性相关。所创建的对象(以及其后销毁的对象)的数量与派生的复杂度成正比。

并不是说继承根本上就是代码性能的绊脚石。我们必须区分全部计算开销、必须开销和计算损失(computional penalty). 全部计算开销是一次计算中所执行的全部指令的总和。必须开销是全部指令的子集,它的结果是必要的。这部分计算是必需的,其余部分即为计算损失。计算损失是可以通过别的设计和实现来消除的那部分计算。

我们不能断言采用了复杂的继承的设计一定是坏的,也不能断定它们总是带来性能损失。我们只能说总的开销会随着派生树规模的增长而增加。如果所有的计算都是有价值的,那么它们都是必须的开销。实际上,继承层次不见得是完善的,在这种情况下,它们很可能会导致计算损失。

对象的复合与继承一样,都引入了与对象创建和销毁有关的类似性能问题。在对象被创建(或销毁)时,必须同时创建(或销毁)它所包含的成员对象。

创建和销毁被包含对象是另一个值得注意的问题:在创建(或销毁)被包含对象时无法阻止子对象的创建(或销毁),因为这是编译器自动强加的步骤。

性能优化经常需要牺牲一些其它软件目标,诸如灵活性、可维护性、成本和重用之类的重要目标经常必须为性能让步。

在C++中,不自觉地在程序开始处预先定义所有对象的做法是一种浪费。因为这样可能会创建一些直到最后都没有用到的对象。在C++中,把变量的创建延迟到第一次使用前。

构造函数和析构函数可以像手工编写的C代码一样有效。然而在实践中,它们经常包含冗余计算。

对象的创建(或销毁)触发对父对象和成员对象的递归创建(或销毁)。

要确保所编写的代码实际使用了所有创建的对象和这些对象所执行的计算。

对象的生命周期不是无偿的。至少对象的创建和销毁会消耗CPU周期。不要随意创建一个对象,除非你打算使用它。通常情况下,要等到需要使用对象的地方再创建它。

编译器必须初始化被包含的成员对象之后再执行构造函数体。你必须在初始化阶段完成成员对象的创建。这可以降低随后在构造函数部分调用赋值操作符的开销。在某些情况下,这样也可以避免临时对象的产生。

以下是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

#include <iostream>
#include <string>
#include <mutex>
#include <chrono>

namespace constructors_destructors_ {

// reference: 《提高C++性能的编程技术》:第二章:构造函数和析构函数

class SimpleMutex { // 单独的锁类
public:
SimpleMutex(std::mutex& mtx) : mymtx(mtx) { acquire(); }
~SimpleMutex() { release(); }

private:
void acquire() { mymtx.lock(); }
void release() { mymtx.unlock(); }

std::mutex& mymtx;
};

class BaseMutex { // 基类
public:
BaseMutex(std::mutex& mtx) {}
virtual ~BaseMutex() {}
};

class DerivedMutex : public BaseMutex {
public:
DerivedMutex(std::mutex& mtx) : BaseMutex(mtx), mymtx(mtx) { acquire(); }
~DerivedMutex() { release(); }

private:
void acquire() { mymtx.lock(); }
void release() { mymtx.unlock(); }

std::mutex& mymtx;

};

class Person1 {
public:
Person1(const char* s) { name = s; } // 隐式初始化和显示赋值

private:
std::string name;
};

class Person2 {
public:
Person2(const char* s) : name(s) {} // 显示初始化

private:
std::string name;
};
} // namespace constructors_destructors_

using namespace constructors_destructors_;

int main()
{
// 测试三种互斥锁的实现
// Note:与书中实验结果有差异,在这里继承对象并不会占用较多的执行时间,在这里这三种所占用时间基本差不多
using namespace std::chrono;
high_resolution_clock::time_point time_start, time_end;
const int cycle_number {100000000};
int shared_counter {0};


{ // 1.直接调用mutex
std::mutex mtx;
shared_counter = 0;
time_start = high_resolution_clock::now();
for (int i = 0; i < cycle_number; ++i) {
mtx.lock();
++shared_counter;
mtx.unlock();
}
time_end = high_resolution_clock::now();
std::cout<< "time spend1: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<< " seconds\n";
}

{ // 2.不从基类继承的独立互斥对象
std::mutex mtx;
shared_counter = 0;
time_start = high_resolution_clock::now();
for (int i = 0; i < cycle_number; ++i) {
SimpleMutex m(mtx);
++shared_counter;
}
time_end = high_resolution_clock::now();
std::cout<< "time spend2: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}

{ // 3.从基类派生的互斥对象
std::mutex mtx;
shared_counter = 0;
time_start = high_resolution_clock::now();
for (int i = 0; i < cycle_number; ++i) {
DerivedMutex m(mtx);
++shared_counter;
}
time_end = high_resolution_clock::now();
std::cout<< "time spend3: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";

}

// 隐式初始化和显示赋值与显示初始化性能对比:使用显示初始化操作要优于使用隐式初始化和显示赋值操作
{ // 1.隐式初始化和显示赋值操作
time_start = high_resolution_clock::now();
for (int i = 0; i < cycle_number; ++i) {
Person1 p("Pele");
}
time_end = high_resolution_clock::now();
std::cout<< "隐式初始化, time spend: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}

{ // 2.显示初始化操作
time_start = high_resolution_clock::now();
for (int i = 0; i < cycle_number; ++i) {
Person2 p("Pele");
}
time_end = high_resolution_clock::now();
std::cout<<"显示初始化, time spend: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}

return 0;
}

利用 godbolt 执行,相应代码见 点击这里 测试结果如下:

1
2
3
4
5
time spend1: 0.0660878 seconds
time spend2: 0.0989312 seconds
time spend3: 0.0757665 seconds
隐式初始化, time spend: 1.88212 seconds
显示初始化, time spend: 3e-08 seconds