內容簡介
《C++性能最佳化指南》是一本C++代碼最佳化指南。作者精選了他在近30年編程生涯中頻繁使用的技術和能夠帶來性能提升效果的技術,旨在讓讀者在提升C++程式的同時,思考最佳化軟體之美。書中主要內容有:代碼最佳化的意義和總原則,與最佳化相關的計算機硬體背景知識,性能分析方法及工具,最佳化字元串的使用,算法、
動態分配記憶體、熱點語句、查找與排序等等的最佳化方法。
《C++性能最佳化指南》適合所有C++程式設計師,也可供其他語言的程式設計師最佳化代碼時作為參考。
圖書目錄
前言 xvii
第1章 最佳化概述 1
1.1 最佳化是軟體開發的一部分 2
1.2 最佳化是高效的 3
1.3 最佳化是沒有問題的 3
1.4 這兒一納秒,那兒一納秒 5
1.5 C++代碼最佳化策略總結 5
1.5.1 用好的編譯器並用好編譯器 6
1.5.2 使用更好的算法 7
1.5.3 使用更好的庫 8
1.5.4 減少記憶體分配和複製 9
1.5.5 移除計算 9
1.5.6 使用更好的數據結構 9
1.5.7 提高並發性 10
1.5.8 最佳化記憶體管理 10
1.6 小結 10
第 2章 影響最佳化的計算機行為 11
2.1 C++所相信的計算機謊言 12
2.2 計算機的真相 12
2.2.1 記憶體很慢 13
2.2.2 記憶體訪問並非以位元組為單位 13
2.2.3 某些記憶體訪問會比其他的更慢 14
2.2.4 記憶體字分為大端和小端 14
2.2.5 記憶體容量是有限的 15
2.2.6 指令執行緩慢 16
2.2.7 計算機難以作決定 16
2.2.8 程式執行中的多個流 16
2.2.9 調用作業系統的開銷是昂貴的 18
2.3 C++也會說謊 18
2.3.1 並非所有語句的性能開銷都相同 18
2.3.2 語句並非按順序執行 18
2.4 小結 19
第3章 測量性能 20
3.1 最佳化思想 21
3.1.1 必須測量性能 21
3.1.2 最佳化器是獵人 21
3.1.3 90/10規則 22
3.1.4 阿姆達爾定律 23
3.2 進行實驗 24
3.2.1 記實驗筆記 26
3.2.2 測量基準性能並設定目標 26
3.2.3 你只能改善你能夠測量的 28
3.3 分析程式執行 28
3.4 測量長時間運行的代碼 30
3.4.1 一點關於測量時間的知識 30
3.4.2 用計算機測量時間 35
3.4.3 克服測量障礙 41
3.4.4 創建stopwatch類 44
3.4.5 使用測試套件測量熱點函式 48
3.5 評估代碼開銷來找出熱點代碼 48
3.5.1 評估獨立的C++語句的開銷 49
3.5.2 評估循環的開銷 49
3.6 其他找出熱點代碼的方法 51
3.7 小結 51
第4章 最佳化字元串的使用:案例研究 53
4.1 為什麼字元串很麻煩 53
4.1.1 字元串是動態分配的 54
4.1.2 字元串就是值 54
4.1.3 字元串會進行大量複製 55
4.2 嘗試最佳化字元串 56
4.2.1 使用複合賦值操作避免臨時字元串 57
4.2.2 通過預留存儲空間減少記憶體的重新分配 57
4.2.3 消除對參數字元串的複製 58
4.2.4 使用疊代器消除指針解引 59
4.2.5 消除對返回的字元串的複製 59
4.2.6 用字元數組代替字元串 60
4.3 嘗試最佳化字元串 62
4.3.1 使用更好的算法 62
4.3.2 使用更好的編譯器 64
4.3.3 使用更好的字元串庫 64
4.3.4 使用更好的記憶體分配器 67
4.4 消除字元串轉換 69
4.4.1 將C字元串轉換為std::string 69
4.4.2 不同字元集間的轉換 70
4.5 小結 70
第5章 最佳化算法 71
5.1 算法的時間開銷 72
5.1.1 情況的時間開銷 74
5.1.2 攤銷時間開銷 74
5.1.3 其他開銷 75
5.2 最佳化查找和排序的工具箱 75
5.3 高效查找算法 75
5.3.1 查找算法的時間開銷 75
5.3.2 當n很小時,所有算法的時間開銷都一樣 76
5.4 高效排序算法 77
5.4.1 排序算法的時間開銷 77
5.4.2 替換在情況下性能較差的排序算法 77
5.4.3 利用輸入數據集的已知特性 78
5.5 最佳化模式 78
5.5.1 預計算 79
5.5.2 延遲計算 80
5.5.3 批量處理 80
5.5.4 快取 80
5.5.5 特化 81
5.5.6 提高處理量 81
5.5.7 提示 81
5.5.8 最佳化期待路徑 82
5.5.9 散列法 82
5.5.10 雙重檢查 82
5.6 小結 82
第6章 最佳化動態分配記憶體的變數 83
6.1 C++變數回顧 84
6.1.1 變數的存儲期 84
6.1.2 變數的所有權 86
6.1.3 值對象與實體對象 86
6.2 C++動態變數API回顧 88
6.2.1 使用智慧型指針實現動態變數所有權的自動化 90
6.2.2 動態變數有運行時開銷 92
6.3 減少動態變數的使用 92
6.3.1 靜態地創建類實例 92
6.3.2 使用靜態數據結構 93
6.3.3 使用std::make_shared 替代new 表達式 97
6.3.4 不要無謂地共享所有權 97
6.3.5 使用“主指針”擁有動態變數 98
6.4 減少動態變數的重新分配 99
6.4.1 預分配動態變數以防止重新分配 99
6.4.2 在循環外創建動態變數 99
6.5 移除無謂的複製 100
6.5.1 在類定義中禁止不希望發生的複製 101
6.5.2 移除函式調用上的複製 102
6.5.3 移除函式返回上的複製 103
6.5.4 免複製庫 105
6.5.5 實現寫時複製慣用法 106
6.5.6 切割數據結構 106
6.6 實現移動語義 107
6.6.1 非標準複製語義:痛苦的實現 107
6.6.2 std::swap():“窮人”的移動語義 108
6.6.3 共享所有權的實體 109
6.6.4 移動語義的移動部分 109
6.6.5 更新代碼以使用移動語義 110
6.6.6 移動語義的微妙之處 111
6.7 扁平數據結構 113
6.8 小結 113
第7章 最佳化熱點語句 115
7.1 從循環中移除代碼 116
7.1.1 快取循環結束條件值 117
7.1.2 使用更高效的循環語句 117
7.1.3 用遞減替代遞增 118
7.1.4 從循環中移除不變性代碼 118
7.1.5 從循環中移除無謂的函式調用 119
7.1.6 從循環中移除隱含的函式調用 121
7.1.7 從循環中移除昂貴的、緩慢改變的調用 123
7.1.8 將循環放入函式以減少調用開銷 123
7.1.9 不要頻繁地進行操作 124
7.1.10 其他最佳化技巧 126
7.2 從函式中移除代碼 126
7.2.1 函式調用的開銷 126
7.2.2 簡短地聲明內聯函式 129
7.2.3 在使用之前定義函式 129
7.2.4 移除未使用的多態性 130
7.2.5 放棄不使用的接口 130
7.2.6 用模板在編譯時選擇實現 133
7.2.7 避免使用PIMPL慣用法 134
7.2.8 移除對DDL的調用 135
7.2.9 使用靜態成員函式取代成員函式 136
7.3 最佳化表達式 137
7.3.1 簡化表達式 137
7.3.2 將常量組合在一起 138
7.3.3 使用更高效的運算符 139
7.3.4 使用整數計算替代浮點型計算 139
7.3.5 雙精度類型可能會比浮點型更快 140
7.3.6 用閉形式替代疊代計算 141
7.4 最佳化控制流程慣用法 142
7.4.1 用switch替代if-else if-else 142
7.4.2 用虛函式替代switch 或if 143
7.4.3 使用無開銷的異常處理 144
7.5 小結 145
第8章 使用更好的庫 146
8.1 最佳化標準庫的使用 146
8.1.2 使用C++標準庫的注意事項 147
8.2 最佳化現有庫 149
8.2.1 改動越少越好 149
8.2.2 添加函式,不要改動功能 150
8.3 設計最佳化庫 150
8.3.1 草率編碼後悔多 150
8.3.2 在庫的設計上,簡約是一種美德 151
8.3.3 不要在庫內分配記憶體 152
8.3.4 若有疑問,以速度為準 152
8.3.5 函式比框架更容易最佳化 152
8.3.6 扁平繼承層次關係 153
8.3.7 扁平調用鏈 153
8.3.8 扁平分層設計 153
8.3.9 避免動態查找 154
8.3.10 留意“上帝函式” 155
8.4 小結 156
第9章 最佳化查找和排序 157
9.1 使用std::map和std::string的鍵值對表 158
9.2 改善查找性能的工具箱 159
9.2.1 進行一次基準測量 160
9.2.2 識別出待最佳化的活動 160
9.2.3 分解待最佳化的活動 160
9.2.4 修改或替換算法和數據結構 161
9.2.5 在自定義抽象上套用最佳化過程 162
9.3 最佳化std::map的查找 163
9.3.1 以固定長度的字元數組作為std::map的鍵 163
9.3.2 以C風格的字元串組作為鍵使用std::map 164
9.3.3 當鍵就是值的時候,使用map的表親std::set 166
9.4 使用頭檔案最佳化算法 167
9.4.1 以序列容器作為被查找的鍵值對表 168
9.4.2 std::find():功能如其名,O(n)時間開銷 169
9.4.3 std::binary_search():不返回值 169
9.4.4 使用std::equal_range()的二分查找 170
9.4.5 使用std::lower_bound()的二分查找 170
9.4.6 自己編寫二分查找法 171
9.4.7 使用strcmp()自己編寫二分查找法 172
9.5 最佳化鍵值對散列表中的查找 173
9.5.1 使用std::unordered_map進行散列 173
9.5.2 對固定長度字元數組的鍵進行散列 174
9.5.3 以空字元結尾的字元串為鍵進行散列 175
9.5.4 用自定義的散列表進行散列 176
9.6 斯特潘諾夫的抽象懲罰 177
9.7 使用C++標準庫最佳化排序 178
9.8 小結 179
第 10章 最佳化數據結構 181
10.1 理解標準庫容器 181
10.1.1 序列容器 182
10.1.2 關聯容器 182
10.1.3 測試標準庫容器 183
10.2 std::vector與std::string 187
10.2.1 重新分配的性能影響 188
10.2.2 std::vector中的插入與刪除 188
10.2.3 遍歷std::vector 190
10.2.4 對std::vector排序 191
10.2.5 查找std::vector 191
10.3 std::deque 191
10.3.1 std::deque中的插入和刪除 193
10.3.2 遍歷std::deque 194
10.3.3 對std::deque 的排序 194
10.3.4 查找std::deque 194
10.4 std::list 194
10.4.1 std::list中的插入和刪除 196
10.4.2 遍歷std::list中 197
10.4.3 對std::list排序 197
10.4.4 查找std::list 197
10.5 std::forward_list 198
10.5.1 std::forward_list中的插入和刪除 199
10.5.2 遍歷std::forward_list 199
10.5.3 對std::forward_list排序 199
10.5.4 查找std::forward_list 199
10.6 std::map與std::multimap 199
10.6.1 std::map中的插入和刪除 200
10.6.2 遍歷std::map 202
10.6.3 對std::map排序 202
10.6.4 查找std::map 203
10.7 std::set與std::multiset 203
10.8 std::unordered_map與std::unordered_multimap 204
10.8.1 std::unordered_map中的插入與刪除 206
10.8.2 遍歷std::unordered_map 207
10.8.3 查找std::unordered_map 207
10.9 其他數據結構 208
10.10 小結 209
第 11章 最佳化I/O 210
11.1 讀取檔案的秘訣 210
11.1.1 創建一個吝嗇的函式簽名 211
11.1.2 縮短調用鏈 213
11.1.3 減少重新分配 213
11.1.4 更大的吞吐量——使用更大的輸入緩衝區 215
11.1.5 更大的吞吐量——一次讀取一行 216
11.1.6 再次縮短函式調用鏈 217
11.1.7 無用的技巧 218
11.2 寫檔案 219
11.3 從std::cin讀取和向std::cout中寫入 220
11.4 小結 220
第 12章 最佳化並發 221
12.1 複習並發 222
12.1.1 並發概述 222
12.1.2 交叉執行 226
12.1.3 順序一致性 226
12.1.4 競爭 227
12.1.5 同步 228
12.1.6 原子性 228
12.2 複習C++並發方式 230
12.2.1 執行緒 230
12.2.2 promise和future 231
12.2.3 異步任務 233
12.2.4 互斥量 234
12.2.5 鎖 235
12.2.6 條件變數 236
12.2.7 共享變數上的原子操作 238
12.2.8 展望未來的C++並發特性 240
12.3 最佳化多執行緒C++程式 241
12.3.1 用std::async替代std::thread 242
12.3.2 創建與核心數量一樣多的可執行執行緒 243
12.3.3 實現任務佇列和執行緒池 244
12.3.4 在單獨的執行緒中執行I/O 245
12.3.5 沒有同步的程式 245
12.3.6 移除啟動和停止代碼 247
12.4 讓同步更加高效 248
12.4.1 減小臨界區的範圍 248
12.4.2 限制並發執行緒的數量 249
12.4.3 避免驚群 250
12.4.4 避免鎖護送 250
12.4.5 減少競爭 250
12.4.6 不要在單核系統上繁忙等待 251
12.4.7 不要永遠等待 252
12.4.8 自己設計互斥量可能會低效 252
12.4.9 限制生產者輸出佇列的長度 252
12.5 並發庫 253
12.6 小結 254
第 13章 最佳化記憶體管理 255
13.1 複習C++ 記憶體管理器API 255
13.1.1 動態變數的生命周期 256
13.1.3 new表達式構造動態變數 259
13.1.4 delete表達式處置動態變數 261
13.1.5 顯式析構函式調用銷毀動態變數 262
13.2 高性能記憶體管理器 263
13.3 提供類專用記憶體管理器 264
13.3.1 分配固定大小記憶體的記憶體管理器 265
13.3.2 記憶體塊分配區 267
13.3.3 添加一個類專用new()運算符 269
13.3.4 分配固定大小記憶體塊的記憶體管理器的性能 270
13.3.5 分配固定大小記憶體塊的記憶體管理器的變化形式 270
13.3.6 非執行緒安全的記憶體管理器是高效的 271
13.4 自定義標準庫分配器 271
13.4.1 C++11分配器 273
13.4.2 C++98分配器的其他定義 274
13.4.3 一個分配固定大小記憶體塊的分配器 278
13.4.4 字元串的分配固定大小記憶體塊的分配器 279
13.5 小結 281
作者介紹 282
封面介紹 282