【C++開発者向け】CUDAとThrustライブラリで始めるGPU高速化入門

C++で大規模なデータを扱う際、その計算速度がボトルネックになっていませんか? 本記事では、その解決策となり得るGPGPU技術について、特にC++開発者が取り組みやすいNVIDIAのCUDAThrustライブラリに焦点を当て、その基本から実践的なサンプルコードまでを丁寧に解説します。

目次

GPUを計算に使う「GPGPU」とは?

まずは、基本となる技術について簡単にご説明します。

  • GPU (Graphics Processing Unit) 本来はコンピューターの画像や映像を描画するために設計されたハードウェアです。内部に非常に多くのコアを持ち、単純な処理を同時に、大規模に実行する「並列処理」が非常に得意です。
  • GPGPU (General-Purpose computing on GPU) このGPUの卓越した並列処理能力を、グラフィックス処理だけでなく、より汎用的な科学技術計算やデータ分析などの分野に応用する技術です。CPUが一つずつ順番に処理するような作業とは対照的に、GPUは大量のデータを一度に処理できるため、アプリケーション全体のパフォーマンスを飛躍的に向上させることが可能です。
  • CUDA (Compute Unified Device Architecture) NVIDIA社が提供する、GPGPUプログラミングのための統合開発環境です。CUDAを利用することで、C++をはじめとする汎用プログラミング言語から、NVIDIA製GPUのパワーを直接引き出すプログラムを作成できます。

C++標準ライブラリのように書ける「Thrust」

CUDAは非常に強力ですが、GPUのメモリ管理などを直接記述する必要があり、習得には少し時間がかかります。そこで、C++開発者にとって心強い味方となるのがThrustライブラリです。

Thrustは、CUDA上で動作するC++テンプレートライブラリであり、C++の標準ライブラリ(STL)によく似たインターフェースを提供しています。std::vectorstd::sortといった機能に慣れ親しんだ方であれば、ほとんど同じ感覚でGPUプログラミングを始めることができます。

Thrustの大きな特徴は、CPUとGPUのメモリ空間を抽象化してくれる点です。

  • thrust::host_vector: ホスト(CPU)側のメモリを管理するコンテナ。
  • thrust::device_vector: デバイス(GPU)側のメモリを管理するコンテナ。

これら2つのコンテナ間で、代入やコピーを行うだけでデータ転送が完了するため、プログラマは煩雑なメモリ操作を意識することなく、計算ロジックそのものに集中できます。

実践!Thrustを使ったGPUソートのサンプルコード

それでは、実際にThrustを使ったコードを見てみましょう。 以下のプログラムは、CPU側で大量の数値を準備し、それをGPUへ転送して高速にソート(並べ替え)、そして結果をCPUへ戻すという一連の流れを実装したものです。

#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <thrust/generate.h>
#include <thrust/sort.h>
#include <thrust/copy.h>
#include <iostream>
#include <vector>
#include <random> // C++11以降のモダンな乱数生成ライブラリ

// 処理するデータ数を定義
const size_t NUM_DATA = 20000000; // 2,000万個

// 乱数を生成するためのシンプルな関数
int generate_random_number() {
    static std::mt19937 engine(std::random_device{}());
    static std::uniform_int_distribution<> dist(1, 1000000);
    return dist(engine);
}

int main() {
    // 1. ホスト(CPU)側でデータコンテナを準備
    thrust::host_vector<int> h_numbers(NUM_DATA);

    // 2. ホスト側でデータをランダムに生成
    std::cout << "Step 1: Generating " << NUM_DATA << " random numbers on the host (CPU)." << std::endl;
    thrust::generate(h_numbers.begin(), h_numbers.end(), generate_random_number);

    // 3. ホストからデバイス(GPU)へデータをコピー
    // この代入操作だけで、CPUメモリからGPUメモリへのデータ転送が実行されます。
    std::cout << "Step 2: Copying data from host to device (GPU)." << std::endl;
    thrust::device_vector<int> d_numbers = h_numbers;

    // 4. デバイス(GPU)上でソートを実行
    // この関数呼び出しは、GPUの多数のコアを利用して並列実行されます。
    std::cout << "Step 3: Sorting data on the device using massive parallelism." << std::endl;
    thrust::sort(d_numbers.begin(), d_numbers.end());

    // 5. ソート済みのデータをデバイスからホストへコピーして戻す
    std::cout << "Step 4: Copying sorted data back from device to host." << std::endl;
    thrust::copy(d_numbers.begin(), d_numbers.end(), h_numbers.begin());

    // 6. 処理完了と結果の確認(ソート後の先頭5件を表示)
    std::cout << "\nProcess finished. Verifying the first 5 sorted numbers:" << std::endl;
    for (size_t i = 0; i < 5; ++i) {
        std::cout << h_numbers[i] << std::endl;
    }

    return 0;
}

コードのポイント解説

  • thrust::host_vector<int> h_numbers(NUM_DATA); CPUのメモリ上に、std::vectorとほぼ同じように使えるh_numbersコンテナを作成します。
  • thrust::device_vector<int> d_numbers = h_numbers; これがThrustの非常に便利な点です。CPU側のhost_vectorをGPU側のdevice_vectorに代入するだけで、必要なデータ転送がバックグラウンドで自動的に行われます。
  • thrust::sort(d_numbers.begin(), d_numbers.end()); C++のstd::sortと全く同じ書式ですが、この処理の実体はGPU上で実行されます。何百万ものデータであっても、GPUの並列処理能力によって極めて高速に完了します。
  • thrust::copy(...) ソート後の結果を、再びCPU側のコンテナに書き戻しています。これにより、後続の処理で計算結果を利用できます。

まとめ

本記事では、CUDAとThrustライブラリを用いて、C++開発者が既存の知識を活かしながらGPGPUプログラミングを始める方法をご紹介しました。

Thrustを利用することで、GPUプログラミングの複雑な部分が抽象化され、まるでC++の標準ライブラリを扱うかのように、GPUの持つ圧倒的な計算能力を活用できます。

データ処理の高速化に課題をお持ちでしたら、その解決策の一つとして、CUDAとThrustの導入を検討してみてはいかがでしょうか。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次