パフォーマンスの最適化
[第]48[課では、]C++17/20[の新機能]について学びました。
[今]、[私たちは]パフォーマンス最適化――[C++プログラムを][より高速に][動作させる]――について学びます。
C++の[強みは]パフォーマンスですが、[それをうまく活用するのは簡単ではありません]。
1. パフォーマンス最適化[概要]
(1) 1.1 [なぜ]最適化が必要なのか?
[原則]:
- [不要]最適化(時期尚早な最適化)
- [まず測定]、[その後]最適化
- 最適化アルゴリズム、[再]最適化コード
(2) 1.2 パフォーマンス[ボトルネック]
| [ボトルネック] | [割合] |
|---|---|
| アルゴリズム | 70% |
| メモリ[アクセス] | 20% |
| [その他] | 10% |
[結論]: 最適化アルゴリズムは、最適化コードよりも[重要]である。
2. メモリ[アラインメント]
(1) 2.1 メモリ[アラインメント]とは何か?
メモリ[アラインメント]とは、[データが]メモリ[内の開始]アドレスが、ある値([通常は]2の冪)の倍数であることを指します。
[なぜ重要なのか]?
- [アラインメントが不適切な]メモリ[アクセス]は[処理速度が低下]する([場合によってはクラッシュ]することもある)
- CPU[アラインメントが整った]メモリ[より高速]
(2) 2.2 例:memory[アラインメントの影響] (難易度 ⭐⭐)
▶ サンプル 1: オブジェクト指向[プログラミングデモ] (難易度 ⭐)
CPP
#include iostream
struct BadAlignment {
char c; // 1lessons of
int i; // 4lessons of(4)
};
struct GoodAlignment {
int i; // 4lessons of
char c; // 1lessons of
};
int main() {
std::cout << "Bad: " << sizeof(BadAlignment) << " bytes" << std::endl;
std::cout << "Good: " << sizeof(GoodAlignment) << " bytes" << std::endl;
return 0;
}
[実行結果]([可能性]):
TEXT
Bad: 8 bytes
Good: 8 bytes
💡 ヒント:
- [[大きな]メンバーを[前に配置する]ことで、パディングを[減らすことができる]
3. [キャッシュ対応]
(1) 3.1 CPU[キャッシュ]
CPUの[キャッシュ比]は、メモリの**[100]倍です。
最適化[原則]:
- local[性原理]:[隣接する]メモリへのアクセス
- [順次アクセス]:[ランダムアクセスよりも高速]
- [キャッシュの無効化を避ける]:[連続していない]メモリへのアクセスを控える
(2) 3.2 例:[行主序] 対 [列主序] (難易度 ⭐⭐⭐)
CPP
#include iostream
### (3) ▶ 2:STL(⭐)
#include vector
#include chrono
int main() {
const int N = 1000;
std::vector<std::vectorint> matrix(N, std::vectorint(N));
// ()
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
matrix[i][j] = 1;
}
}
auto end = std::chrono::high_resolution_clock::now();
auto row_time = std::chrono::durationdouble(end - start).count();
// ()
start = std::chrono::high_resolution_clock::now();
for (int j = 0; j < N; j++) {
for (int i = 0; i < N; i++) {
matrix[i][j] = 1;
}
}
end = std::chrono::high_resolution_clock::now();
auto col_time = std::chrono::durationdouble(end - start).count();
std::cout << ":" << row_time << "..." << std::endl;
std::cout << ":" << col_time << "..." << std::endl;
return 0;
}
4. コンパイル[器]の最適化
(1) 4.1 最適化[レベル]
| [レベル] | [マーク] | [説明] |
|---|---|---|
| O0 | [なし] | [非]最適化(デバッグ[用]) |
| O1 | -O1 |
[基本]最適化 |
| O2 | -O2 |
[おすすめ]([バランス]) |
| O3 | -O3 |
[徹底的な]最適化 |
| Os | -Os |
最適化[大小] |
(2) 4.2 例:コンパイル[器]の最適化[効果] (難易度 ⭐)
CPP
#include iostream
int main() {
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
std::cout << sum << std::endl;
return 0;
}
コンパイル:
BASH
g++ -O0 main.cpp #
g++ -O2 main.cpp # ()
5. パフォーマンス[分析ツール]
(1) 5.1 [よく使うツール]
| [ツール] | [プラットフォーム] | [説明] |
|---|---|---|
| gprof | Linux | GCCのパフォーマンス[分析] |
| perf | Linux | Linuxperformance[分析ツール] |
| Valgrind | [クロスプラットフォーム] | メモリ[分析] |
| Visual Studio Profiler | Windows | VS[標準搭載] |
(2) 5.2 例:[用]chrono[時間を測定する] (難易度 ⭐)
CPP
#include iostream
#include chrono
int main() {
auto start = std::chrono::high_resolution_clock::now();
// ...
long sum = 0;
for (int i = 0; i < 100000000; i++) {
sum += i;
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::durationdouble(end - start).count();
std::cout << ":" << duration << "..." << std::endl;
return 0;
}
(2) 6.2 アルゴリズム[レベル]
| [コツ] | [説明] |
|---|---|
| [適切なデータ構造の選択] | ベクトル vs リスト vs マップ |
| [適切な]アルゴリズム | sort 対 partial_sort |
| [不要なコピーを避ける] | [参照]、[移動] |
❓ よくある質問
Q 最適化は[必ず効果があるのでしょうか]?
A [必ずしもそうとは限りません]。[まず測定を行い]、[ボトルネックを特定してから]最適化を行うべきです。
Q C++は[一定程度]Pythonより[速い]ですか?
A [必ずしもそうとは限りません]。[もし]アルゴリズムが[同じ]なら、C++は[通常、速い]です。[しかし、不適切な]アルゴリズムを使った場合、C++は[遅くなる可能性もあります]。
Q [ボトルネックがどこにあるかをどう判断すればよいですか]?
A [パフォーマンス]分析ツール(プロファイラ)を[使用]します。
📖 まとめ
| [知識ポイント] | [要点] |
|---|---|
| メモリ[アラインメント] | [パディングの削減] |
| [キャッシュに優しい] | [順次アクセス]、local[性] |
| コンパイラの最適化 | -O2 [おすすめ] |
| パフォーマンス[分析] | [事前測定],[その後]最適化 |
| 最適化[テクニック] | 移動セマンティクス、emplace_back |
📝 練習問題
-
初心者(難易度 ⭐):vector と list をそれぞれ使って 100000 個の整数を格納し、末尾への挿入とランダムアクセスの所要時間の差を比較する。
-
**中級(難易度 ⭐⭐):[値渡し]と[参照渡し]の[パフォーマンスの][違い]を検証する。[大きな]構造体([vectorint(10000)を含む])を処理する[関数]を書き、[それぞれ]値渡し[と] const 参照渡し[を用いて]実行時間を測定する。
-
**上級(難易度 ⭐⭐⭐):[使用] std::chrono [高精度計時]、[比較] forループ、STLのfor_each、[範囲] における [3種類の反復処理方法の] パフォーマンス[の差異]。[分析結果]。
- コンパイラの最適化[オプション]:-O0/-O1/-O2/-O3/-Os
- [コピー削減]:参照渡し、移動セマンティクス
- コンテナ[選択]:ベクトル [連続]メモリ[キャッシュ効率]
- [インライン化]関数[削減]関数[呼び出しオーバーヘッド]
- プロファイル [指導]最適化:[まずボトルネックを特定してから]最適化
[次のレッスン]:ユニットテスト(#50)



