C++: パフォーマンス最適化

最終更新:2026-08-31

レッスン48では、C++17/20について学びました。

ここでは、パフォーマンス最適化について学びます。

C++の強みはパフォーマンスです。しかし、最適化には正しい知識とツールが必要です。


1. 最適化の基本原則

(1) 1.1 なぜ最適化が必要?

原則:

  1. 早すぎる最適化は避ける(Premature Optimization)
  2. 測ってから最適化
  3. アルゴリズムを最適化、コードを最適化

(2) 1.2 パフォーマンス要因

要因 影響
アルゴリズム 70%
メモリアクセス 20%
その他 10%

要点: アルゴリズムの最適化はコード最適化より重要。




2. メモリアライメント

(1) 2.1 メモリアライメントとは?

メモリアライメントはメモリアドレスの配置方法で、CPUの読み書き効率に影響します。

なぜ重要?


(2) 2.2 例:メモリアライメントの影響(難易度 ⭐⭐)

▶ サンプル 1:オブジェクト指向プログラミング(難易度 ⭐)

CPP
#include <iostream>

struct BadAlignment {
    char c; // 1バイト
    int i; // 4バイト(パディング3バイト)
};

struct GoodAlignment {
    int i; // 4バイト
    char c; // 1バイト
};

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倍高速です。

最適化のポイント:

  1. 局所性:メモリの近い場所にアクセス
  2. 順次アクセス:ランダムアクセスより高速
  3. キャッシュミスを避ける:メモリの飛び飛びアクセスを避ける

(2) 3.2 例:行優先 vs 列優先(難易度 ⭐⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <chrono>

int main() {
    const int N = 1000;
    std::vector<std::vector<int>> matrix(N, std::vector<int>(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::duration<double>(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::duration<double>(end - start).count();
    
    std::cout << "行優先:" << row_time << " 秒" << std::endl;
    std::cout << "列優先:" << col_time << " 秒" << std::endl;
    
    return 0;
}



4. コンパイラ最適化

(1) 4.1 最適化フラグ

フラグ 説明
-O0 最適化なし(デバッグ用)
-O1 基本的な最適化
-O2 推奨(バランス)
-O3 積極的な最適化
-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 Linux標準プロファイラ
Valgrind クロスプラットフォーム メモリ分析
Visual Studio Profiler Windows VS標準

(2) 5.2 例:std::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::duration<double>(end - start).count();
    
    std::cout << "実行時間:" << duration << " 秒" << std::endl;
    
    return 0;
}



6. コード最適化の実践

(1) 6.1 コードレベル

テクニック 説明
ムーブセマンティクス コピーを避ける
emplace_back 一時オブジェクトを避ける
reserve vectorの再割り当てを避ける
unordered_map ハッシュ、O(1)検索

(2) 6.2 アルゴリズムレベル

テクニック 説明
適切なコンテナ vector vs list vs map
適切なアルゴリズム sort vs partial_sort
値渡しを避ける 参照を使用

▶ サンプル 2:Vector Reserve最適化(難易度 ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <chrono>

int main() {
    const int N = 1000000;

    // reserveなし
    auto start = std::chrono::high_resolution_clock::now();
    std::vector<int> v1;
    for (int i = 0; i < N; i++) {
        v1.push_back(i);
    }
    auto end = std::chrono::high_resolution_clock::now();
    double time1 = std::chrono::duration<double>(end - start).count();

    // reserveあり
    start = std::chrono::high_resolution_clock::now();
    std::vector<int> v2;
    v2.reserve(N);
    for (int i = 0; i < N; i++) {
        v2.push_back(i);
    }
    end = std::chrono::high_resolution_clock::now();
    double time2 = std::chrono::duration<double>(end - start).count();

    std::cout << "reserveなし: " << time1 << " 秒" << std::endl;
    std::cout << "reserveあり: " << time2 << " 秒" << std::endl;

    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
reserveなし: 0.05 秒
reserveあり: 0.02 秒

▶ サンプル 3:参照渡し最適化(難易度 ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <chrono>

struct BigData {
    std::vector<int> data;
    BigData(int n) : data(n, 0) {}
};

// 遅い:値渡し(vector全体をコピー)
void processByValue(BigData d) {
    d.data[0] = 1;
}

// 高速:参照渡し(コピーなし)
void processByRef(BigData& d) {
    d.data[0] = 1;
}

int main() {
    BigData big(10000);
    
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000; i++) {
        processByValue(big);
    }
    auto end = std::chrono::high_resolution_clock::now();
    double value_time = std::chrono::duration<double>(end - start).count();
    
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000; i++) {
        processByRef(big);
    }
    end = std::chrono::high_resolution_clock::now();
    double ref_time = std::chrono::duration<double>(end - start).count();
    
    std::cout << "値渡し: " << value_time << " 秒" << std::endl;
    std::cout << "参照渡し: " << ref_time << " 秒" << std::endl;
    
    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
値渡し: 0.8 秒
参照渡し: 0.001 秒

❓ よくある質問

Q:最適化は本当に必要? A: 場合によります。プロファイラで測ってから判断。


Q:C++はPythonより速い? A:一般的にはいえ。アルゴリズムと実装次第。C++の利点は低レベル制御。


Q:どのコンパイラフラグを使う? A:開発中は -O0、リリースは -O2 または -O3


📖 まとめ

トピック 要点
メモリアライメント パディングを削減
キャッシュ最適化 順次アクセス、局所性
コンパイラ最適化 -O2 を推奨
パフォーマンス測定 測ってから最適化
コード最適化 ムーブセマンティクス、emplace_back

📝 練習問題

  1. 初級(難易度 ⭐): vectorとlistで100000個の要素を追加・削除し、パフォーマンスを比較。

  2. 中級(難易度 ⭐⭐): 値渡しと参照渡しのパフォーマンスを比較。大きな構造体(vector<int>(10000))を渡す関数を書き、値渡しとconst参照渡しの速度を測定。

  3. 上級(難易度 ⭐⭐⭐): std::chronoを使って、forループ、STL for_each、Rangesのパフォーマンスを比較測定。



次のレッスン:ユニットテスト(#50)

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%