C++で最適化する際のパフォーマンスチューニングの原則と、より高速かつリソースの消費が最低限で済む効率の良いコードの書き方を指南します。まず、ボトルネックを特定し、どのような最適化を適用すべきかを紹介。小手先のテクニックだけでなく、データ構造の選択、アルゴリズムの選択などの深い部分の理解を促し、さらにはライブラリの選択や並行処理、入出力、メモリ管理まで、多方面からの最適化によりプログラムの効率化を図ります。スマフォから大規模サーバまで応用可能な、汎用性の高い内容です。
https://www.ohmsha.co.jp/book/9784873117928/
正誤表やDLデータ等がある場合はこちらに掲載しています
日本語版へ寄せて
訳者まえがき
まえがき
1章 最適化とは
1.1 最適化はソフトウェア開発の一部
1.2 最適化は効果的
1.3 最適化しても大丈夫
1.4 こちらにナノ秒、あちらにナノ秒
1.5 最適化C++コード戦略のまとめ
1.5.1 より良いコンパイラを使う、コンパイラをよりうまく使う
1.5.2 より良いアルゴリズムを使う
1.5.3 より良いライブラリを使う
1.5.4 メモリ割り当てとコピーを減らす
1.5.5 計算を取り除く
1.5.6 より良いデータ構造を使う
1.5.7 並行性を高める
1.5.8 メモリ管理を最適化する
1.6 まとめ
2章 最適化に影響するマシンの振る舞い
2.1 C++の嘘と真実
2.2 コンピュータについての真実
2.2.1 メモリは遅い
2.2.2 メモリはバイト単位でアクセスされているのではない
2.2.3 メモリアクセスによっては他より遅いことがある
2.2.4 メモリ語にはビッグエンディアンとリトルエンディアンがある
2.2.5 メモリ容量は有限
2.2.6 命令実行は遅い
2.2.7 コンピュータは決定が下手だ
2.2.8 プログラム実行に複数のストリームがある
2.2.9 オペレーティングシステム呼び出しは高価
2.3 C++も嘘をつく
2.3.1 文がすべて高価なわけではない
2.3.2 文は順番に実行されない
2.4 まとめ
3章 性能を測定する
3.1 最適化マインドセット
3.1.1 性能は計測されねばならない
3.1.2 最適化技術者は大物狙いの狩人だ
3.1.3 90/10ルール
3.1.4 アムダールの法則
3.2 実験を行う
3.2.1 実験ノートをつける
3.2.2 ベースライン性能を測定し目標を立てる
3.2.3 測定できるものだけが改善できる
3.3 プログラム実行のプロファイルを取る
3.4 長時間実行コードを計測する
3.4.1 時間計測の「ちょっとした学習」
3.4.2 コンピュータで時間を測る
3.4.3 測定する際の障害を乗り越える
3.4.4 Stopwatchクラスを作る
3.4.5 テストハーネスでホットな関数の時間を測る
3.5 ホットなコードを探し出すためにコードのコストを見積もる
3.5.1 C++文それぞれのコストを見積もる
3.5.2 ループのコストを見積もる
3.6 ホットスポットを探し出す他の方法
3.7 まとめ
4章 文字列使用を最適化する:事例研究
4.1 なぜ文字列が問題か
4.1.1 文字列は動的に割り当てられる
4.1.2 文字列は値だ
4.1.3 文字列はコピーが多い
4.2 文字列最適化の最初の試み
4.2.1 一時ストレージをなくすために変更文字列演算を使う
4.2.2 ストレージを確保することで再割り当てを減らす
4.2.3 文字列引数のコピーをなくす
4.2.4 イテレータを使ってポインタ参照外しをなくす
4.2.5 文字列値の戻り値のコピーをなくす
4.2.6 文字列ではなく文字配列を使う
4.2.7 最初の最適化努力のまとめ
4.3 文字列最適化の第2の試み
4.3.1 より良いアルゴリズムを使う
4.3.2 より良いコンパイラを使う
4.3.3 より良い文字列ライブラリを使う
4.3.4 より良いアロケータを使う
4.4 文字列変換をなくす
4.4.1 C文字列からstd::stringへの変換
4.4.2 文字符号化間での変換
4.5 まとめ
5章 アルゴリズムを最適化する
5.1 アルゴリズムの時間コスト
5.1.1 最良時、平均時、および最悪時コスト
5.1.2 ならし時間コスト
5.1.3 他のコスト
5.2 探索と整列を最適化するツールキット
5.3 効率的探索アルゴリズム
5.3.1 探索アルゴリズムの時間コスト
5.3.2 すべての探索はn が小さいなら等しい
5.4 効率的整列アルゴリズム
5.4.1 整列アルゴリズムの時間コスト
5.4.2 最悪時性能のソートを置き換える
5.4.3 入力データの既知の特性を活用する
5.5 最適化のパターン
5.5.1 事前計算
5.5.2 遅延計算
5.5.3 バッチ処理
5.5.4 キャッシュ
5.5.5 特殊化
5.5.6 大量データ処理
5.5.7 ヒント
5.5.8 期待パスを最適化する
5.5.9 ハッシング
5.5.10 ダブルチェック
5.6 まとめ
6章 動的変数割り当てを最適化する
6.1 C++ 変数の復習
6.1.1 変数の記憶域期間
6.1.2 変数の所有権
6.1.3 値オブジェクトとエンティティオブジェクト
6.2 C++動的変数APIの復習
6.2.1 スマートポインタは動的変数の所有権を自動化する
6.2.2 動的変数には実行時のコストがかかる
6.3 動的変数の使用を減らす
6.3.1 クラスインスタンスを静的に作る
6.3.2 静的データ構造を使う
6.3.3 new の代わりにstd::make_sharedを使う
6.3.4 所有権を不必要に共有しない
6.3.5 「マスターポインタ」を使って動的変数を所有する
6.4 動的変数の再割り当てを減らす
6.4.1 動的変数を前もって割り当てて再割り当てを防ぐ
6.4.2 動的変数をループの外で作る
6.5 不必要なコピーをなくす
6.5.1 望まないコピーをクラス定義で無効にする
6.5.2 関数呼び出しでのコピーをなくす
6.5.3 関数から戻るときのコピーをなくす
6.5.4 コピーフリーライブラリ
6.5.5 「コピーオンライト」イディオムを実装する
6.5.6 データ構造をスライスする
6.6 ムーブセマンティクスの実装
6.6.1 非標準的コピーセマンティクス:痛みを伴うハッキング
6.6.2 std::swap():貧乏人のムーブセマンティクス
6.6.3 エンティティの共有所有権
6.6.4 ムーブセマンティクスの移動部分
6.6.5 ムーブセマンティクスを用いてコードを更新する
6.6.6 ムーブセマンティクスの難しいところ
6.7 データ構造をフラットにする
6.8 まとめ
7章 ホットな文を最適化する
7.1 ループからコードを取り除く
7.1.1 ループの最後の値をキャッシュする
7.1.2 より効率的なループ文を使う
7.1.3 カウントアップの代わりにカウントダウンする
7.1.4 不変なコードをループから取り除く
7.1.5 不必要な関数呼び出しをループから取り除く
7.1.6 ループから隠された関数呼び出しを取り除く
7.1.7 高価で値がゆっくり変わる呼び出しをループから取り除く
7.1.8 呼び出しのオーバーヘッドを減らすためにループを関数の内側に押し込める
7.1.9 処理の頻度を減らす
7.1.10 その他にできること
7.2 関数のコードを取り除く
7.2.1 関数呼び出しのコスト
7.2.2 短い関数はインラインと宣言する
7.2.3 最初に使う前に関数が定義されている状態にする
7.2.4 使っていないポリモルフィズムをなくす
7.2.5 使っていないインタフェースを取り除く
7.2.6 テンプレートでコンパイル時に実装を選ぶ
7.2.7 PIMPLイディオムの使用を止める
7.2.8 DLL呼び出しをなくす
7.2.9 メンバ関数の代わりに静的メンバ関数を使う
7.2.10 仮想デストラクタを基底クラスに移す
7.3 式を最適化する
7.3.1 式を単純化する
7.3.2 定数をまとめる
7.3.3 より安価な演算子を使う
7.3.4 浮動小数点算術よりも整数算術を使う
7.3.5 double はfloat より速い場合がある
7.3.6 反復計算を閉形式で置き換える
7.4 制御フローイディオムを最適化する
7.4.1 if-else if-elseよりもswitchを使う
7.4.2 switchやifの代わりに仮想関数を使う
7.4.3 コストなしの例外処理を使う
7.5 まとめ
8章 優れたライブラリを使う
8.1 標準ライブラリ利用を最適化する
8.1.1 C++標準ライブラリの哲学
8.1.2 C++標準ライブラリを使うときの問題
8.2 既存ライブラリを最適化する
8.2.1 変更をできる限り減らす
8.2.2 機能を変更するよりは関数を追加する
8.3 最適化したライブラリの設計
8.3.1 急いでコード化し、ゆっくり後悔せよ
8.3.2 節約はライブラリ設計での美徳
8.3.3 メモリ割り当て決定をライブラリの外で行う
8.3.4 迷ったらライブラリのコードは速度優先にする
8.3.5 関数はフレームワークより最適化しやすい
8.3.6 継承階層を平坦にする
8.3.7 呼び出し連鎖を平坦にする
8.3.8 設計の層を平坦にする
8.3.9 動的参照を止める
8.3.10 「全能関数」に注意
8.4 まとめ
9章 探索と整列を最適化する
9.1 std::mapとstd::stringを使ったキー・値テーブル
9.2 探索性能を改善するツールキット
9.2.1 ベースライン測定を行う
9.2.2 最適化する作業を仕分ける
9.2.3 最適化する作業を分解する
9.2.4 アルゴリズムとデータ構造を変更または置き換える
9.2.5 カスタム抽象化に最適化プロセスを使う
9.3 std::mapを使って探索を最適化する
9.3.1 std::mapで固定サイズ文字配列キーを使う
9.3.2 std::mapでCスタイル文字列キーを使う
9.3.3 キーが値の中にあればマップの従兄弟のstd::setを使う
9.4 ヘッダを使って探索を最適化する
9.4.1 シーケンスコンテナでキー・値テーブルを探索する
9.4.2 std::find():名前は明らか、O(n) 時間コスト
9.4.3 std::binary_search()は値を返さない
9.4.4 std::equal_range()を使った二分探索
9.4.5 std::lower_bound()を使った二分探索
9.4.6 自前コードで二分探索
9.4.7 strcmp()を使った自前コードで二分探索
9.5 キー・値ハッシュテーブルで探索を最適化する
9.5.1 std::unordered_mapでハッシュする
9.5.2 固定文字配列キーでハッシュする
9.5.3 ナル終端文字列キーでハッシュする
9.5.4 カスタムハッシュテーブルでハッシュする
9.6 ステパノフの抽象化ペナルティ
9.7 C++標準ライブラリを使って整列を最適化する
9.8 まとめ
10章 データ構造を最適化する
10.1 標準ライブラリコンテナをよく知る
10.1.1 シーケンスコンテナ
10.1.2 連想コンテナ
10.1.3 標準ライブラリコンテナで実験する
10.2 std::vectorとstd::string
10.2.1 再割り当ての性能結果
10.2.2 std::vectorでの挿入削除
10.2.3 std::vectorでのイテレーション
10.2.4 std::vectorを整列する
10.2.5 std::vectorで探索する
10.3 std::deque
10.3.1 std::dequeでの挿入削除
10.3.2 std::dequeでのイテレーション
10.3.3 std::dequeを整列する
10.3.4 std::dequeで探索する
10.4 std::list
10.4.1 std::listでの挿入削除
10.4.2 std::listでのイテレーション
10.4.3 std::listを整列する
10.4.4 std::listで探索する
10.5 std::forward_list
10.5.1 std::forward_listでの挿入削除
10.5.2 std::forward_listでのイテレーション
10.5.3 std::forward_listを整列する
10.5.4 std::forward_listで探索する
10.6 std::mapとstd::multimap
10.6.1 std::mapでの挿入削除
10.6.2 std::mapでイテレーション
10.6.3 std::mapを整列する
10.6.4 std::mapで探索する
10.7 std::setとstd::multiset
10.8 std::unordered_mapとstd::unordered_multimap
10.8.1 std::unordered_mapでの挿入削除
10.8.2 std::unordered_mapでのイテレーション
10.8.3 std::unordered_mapで探索する
10.9 他のデータ構造
10.10 まとめ
11章 I/O を最適化する
11.1 ファイル読み込みのためのレシピ
11.1.1 節約的関数シグネチャを作る
11.1.2 呼び出し連鎖を縮める
11.1.3 再割り当てを減らす
11.1.4 より大きな単位で―より大きな入力バッファを使う
11.1.5 より大きな単位にする―一度に1行読み込む
11.1.6 呼び出し連鎖を再度縮める
11.1.7 役に立たない事柄
11.2 ファイル書き出し
11.3 std::cinから読み込みstd::coutに書き出す
11.4 まとめ
12章 並行性を最適化する
12.1 C++ 並行機能の復習
12.1.1 さまざまな並行処理機能の紹介
12.1.2 並行プログラムのインタリーブ実行
12.1.3 逐次一貫性
12.1.4 競合
12.1.5 同期
12.1.6 アトミック性
12.2 C++並行処理機能の復習
12.2.1 スレッド
12.2.2 promiseとfuture
12.2.3 非同期タスク
12.2.4 mutex
12.2.5 ロック
12.2.6 条件変数
12.2.7 共有変数のアトミック演算
12.2.8 今後予定されている将来のC++並行処理機能
12.3 C++スレッドプログラムの最適化
12.3.1 std::threadよりstd::asyncを使う
12.3.2 コアと同じ個数の実行可能スレッドを作る
12.3.3 タスクキューとスレッドプールを実装する
12.3.4 別のスレッドでI/Oを実行する
12.3.5 同期なしのプログラム
12.3.6 起動とシャットダウンからコードを除く
12.4 同期をより効率的に行う
12.4.1 クリティカルセクションの範囲を減らす
12.4.2 並行スレッドの個数を限定する
12.4.3 途方もない大群を避ける
12.4.4 ロック護送列を避ける
12.4.5 競合を減らす
12.4.6 単一コアシステムでビジーウェイトしない
12.4.7 永遠に待ってはならない
12.4.8 自分でmutexを作ると非効率
12.4.9 生産者出力キューの長さを制限する
12.5 並行処理ライブラリ
12.6 まとめ
13章 メモリ管理を最適化する
13.1 C++メモリ管理APIの復習
13.1.1 動的変数のライフサイクル
13.1.2 メモリ管理関数がメモリを割り当てて解放する
13.1.3 new式で動的変数を作る
13.1.4 delete式で動的変数を削除する
13.1.5 明示的デストラクタ呼び出しは動的変数を破壊する
13.2 高性能メモリマネージャ
13.3 クラス専用メモリマネージャを提供する
13.3.1 固定サイズブロックメモリマネージャ
13.3.2 ブロックアリーナ
13.3.3 クラス専用operator new()を追加する
13.3.4 固定ブロックメモリマネージャの性能
13.3.5 さまざまな固定ブロックメモリマネージャ
13.3.6 非スレッドセーフメモリマネージャは効率的だ
13.4 カスタムな標準ライブラリアロケータを提供する
13.4.1 極小C++11アロケータ
13.4.2 C++98アロケータの追加定義
13.4.3 固定ブロックアロケータ
13.4.4 文字列の固定ブロックアロケータ
13.5 まとめ
索引