📌 この記事は Zenn に投稿した内容のアーカイブです。

はじめに

本記事では、私自身が経験したことや、信頼できる実践的な知見を元に、C++で高速な数値処理や行列演算を行う際に意識すべき最適化ポイント をわかりやすく整理しました。
HPCやリアルタイム演算に関心のある方に参考になれば幸いです。


1. メモリのアライメントとキャッシュ効率

アライメント(__attribute__((aligned(n))

  • CPUはキャッシュライン(通常64バイト)単位 でメモリを読み込む。

  • メモリが正しく整列されていないと、キャッシュミスやfalse sharing が発生し、性能が大幅に低下。

  • GCC/Clangなら __attribute__((aligned(32))) など、MSVCでは __declspec(align(32)) を使う。

連続したメモリを使用する(std::vector<T>など)

  • 行列は行優先(row-major)の順番で連続配置することで、CPUの事前読み込み(prefetch)が効きやすくなる。

  • std::vector<std::vector<float>> のような非連続構造は避ける。


2. メモリ確保の最適化:高速メモリアロケータ

標準の mallocnew は汎用的で安全ですが、性能面ではボトルネックになることがあります。

おすすめのアロケータ

  • tcmalloc (Google製、スレッドセーフ)

  • jemalloc (Facebook製、ゲームやサーバーで人気)

  • Hoard (スケーラブルなスレッド対応)

これらを使うと、多スレッド環境でのアロケーション性能が大幅に向上 します。


3. 行列演算の高速化手法

Strassenアルゴリズム(O(n^2.81))

  • 通常のO(n³)よりも理論上速い。

  • 再帰的に分割しながら計算することで、掛け算回数を削減。

ブロック分割(Block-based Multiplication)

  • 大きな行列を小さなサブブロックに分けて計算 すると、キャッシュが有効活用できる。

  • 代表的なライブラリ(後述するBLASなど)ではこの最適化が組み込まれている。


4. 並列計算(マルチスレッド化)

C++11の標準スレッド

  • std::thread, std::async を使って並列化可能。

  • シンプルな制御だが、スレッド数の管理や同期は自前で対応が必要。

OpenMP

  • #pragma omp parallel for のように、簡潔にfor文を並列化。

  • コンパイラ依存(GCC/Clang/MSVC全て対応)。

Intel TBB(Thread Building Blocks)

  • 動的なタスクスケジューリング・負荷分散・ワークスティーリングが可能。

  • 不規則な処理を高速並列化したいときに強力。


5. SIMD命令の活用(SSE / AVX)

  • SIMD(Single Instruction, Multiple Data) を使うと、1命令で複数のデータを同時処理できる。

  • 例:AVX2なら、32bit float を8個同時に加算や乗算 できる。

    #include <immintrin.h>

    __m256 a = _mm256_load_ps(…); __m256 b = _mm256_load_ps(…); __m256 c = _mm256_mul_ps(a, b);

  • -mavx2 などのフラグが必要(GCC/Clang)。

  • メモリアライメント必須。


6. ループ内の不要な計算を減らす

for (int i = 0; i < N; ++i) {
    double val = expensive_func(); // 毎回呼び出すのは無駄
    data[i] += val * i;
}

↓ 最適化後:

double val = expensive_func(); //  ループ外に出す
for (int i = 0; i < N; ++i) {
    data[i] += val * i;
}

ループの中は徹底的に「軽く」する ことが原則です。


7. コンパイラ最適化フラグの活用

  • -O3: 高レベル最適化(ループ展開、ベクトル化など)

  • -Ofast: -O3 よりさらに積極的。-ffast-math を含み、浮動小数点の精度保証を緩和。

  • -march=native: 現在のCPUアーキテクチャに最適化。

注意-Ofast はNaNの扱いや丸め誤差が変わる場合があるため、数値の正確性が重要な場合は注意。


8. 高速な数値ライブラリの活用(BLAS / LAPACK)

自分で行列計算を書くのではなく、信頼できる数学ライブラリを使うのがベストです。

  • BLAS(Basic Linear Algebra Subprograms) :行列の基本操作

  • LAPACK :LU分解やSVDなどの高度なアルゴリズム

  • 実装例:

    • Intel MKL(無料だがプロプライエタリ)

    • OpenBLAS(オープンソース)

    • cuBLAS(GPU向け)


9. プロファイリングと性能解析

使用ツール

ツール名内容
gprof関数単位の呼び出し・時間分析
Valgrindメモリリーク・キャッシュグラインド
Intel VTune本格的なCPUマイクロアーキ解析
perf (Linux)ハードウェアカウンタベースの分析

最適化の第一歩は「測定すること」 。予想ではなく、データでボトルネックを特定する のが重要です。


おわりに

C++による高性能計算は奥が深いですが、正しい知識と習慣を身につければ、数倍〜数十倍の性能差 を実現することも可能です。
今回紹介した内容はほんの入り口にすぎませんが、実際の開発現場でも非常に役立つポイントです。