2014年4月28日月曜日

C/C++ 処理の流れ その4 並列処理

並列処理は同時に処理を行うことで、長い処理の間にユーザーが別の処理を行えるようにするなど、使い勝手の面で重要でした。

さらに最近では、CPUも単体での性能は頭打ちになってきており、CPUを複数使って、並列に処理することで高速化することも重要となってきました。

基本的にC/C++言語での処理の流れは、単体での処理となっており、並列処理はOS依存であったりライブラリであったりと、同じような処理を行うにも方法が様々です。

最近の新しいバージョンのC/C++では、標準で並列処理をライブラリで持っていますが、普及はまだまだのような気がします。

並列処理にはプロセスとスレッドとコルーチンがありますが、一番一般的なスレッドを扱う方法について説明します。

Win32でのスレッド(C言語)


Windowsの場合は<process.h>の_beginthredex()関数を使用してスレッドを作成し、並列処理を行います。CreateThread()というWin32APIもあるのですが、こちらよりbeginthreadex()関数の方が良いです(標準ライブラリの初期化をため、標準ライブラリを使用する場合は必須です)。

#include <stdio.h>
#include <process.h>
#include <windows.h>

unsigned int __stdcall print_hello(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("Hello\n");
                Sleep(10);
        }
        return 0;
}

unsigned int __stdcall print_world(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("World\n");
                Sleep(10);
        }
        return 0;
}

int main()
{
        HANDLE th;

        th = (HANDLE)_beginthreadex(NULL, 0, &print_hello, NULL, 0, NULL);
        print_world(NULL);
        WaitForSingleObject(th, INFINITE);        
        return 0;
}

上記では、分かりやすくするためにスレッドの作成に失敗した場合等のエラー処理は全く行っていないので注意してください。

_beginthraedexで、新しいスレッド(並列処理)を作成して、その並列処理でprint_hello()関数を実行します。元の処理はそのまま次に進みprint_world()関数を実行します。そのため、print_hello()関数とprint_world()関数は同時に行われます。

最後にWaitForSingleObject()関数で、並列処理のスレッドの終了を待って、プログラムを終了します。 これを行わないと、終わる前にプロセスが終了してしまい、スレッドの処理は中断されることになります。

その他、本来であれば、printf()の処理は同じ端末に対して行うので、本来であれば排他処理が必要なのかもしれません。排他処理は同じデータに並列処理が同時にアクセスし、データが異常になるのを防ぐために行います。

詳細は、Microsoftの開発者用のサイトを参照してください。


Linux(POSIX)でのスレッド(C言語)



LinuxやMac OS X等のUNIX系ではPOSIXという規格があり、その中でpthreadライブラリが定義されています。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *print_hello(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("Hello\n");
                usleep(10);
        }
        return NULL;
}

void *print_world(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("World\n");
                usleep(10);
        }
        return NULL;
}

int main()
{
        pthread_t th;

        pthread_create(&th, NULL, &print_hello, NULL);
        print_world(NULL);
        pthread_join(th, NULL);        
        return 0;
}

コンパイルをするときには -lpthreadのオプションを追加する必要があります。関数名や引数がWindowsとは違いますが、やっていることは同じなのでほとんど同じ記述になります。

またMinGW環境でも利用可能です。


C++11でのスレッド


C++の2011年番のC++11では<thread>にstd::threadが追加されました。以下がprint_hello()関数とprint_world()関数を並列動作させる例です。残念ながらMinGW環境ではコンパイル時にエラーとなります。

#include <stdio.h>
#include <thread>

void print_hello()
{
        for(int i = 0; i < 20; i++) {
                printf("Hello/n");
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
}

void print_world()
{
        for(int i = 0; i < 20; i++) {
                printf("World/n");
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
}

int main()
{
        std::thread th(print_hello);
        print_world();
        th.join();
        return 0;
}


C11でのスレッド


C言語でも2011年版のC11において、<threads.h>にスレッドを扱うthrd_t型や、thrd_create()関数、thrd_jon()関数が追加されています。残念ながらgccでもサポートがまだのようです(Fedora20, Mingwでthraeds.hが無いことを確認)。


その他のライブラリ



並列プログラミングは、どちらかというと歴史が新しく色々なライブラリがあります。例えば以下です。C++11のstd::threadの元となったライブラリでC++98でも使用できます。
  • boost::thread

 std::threadなどの標準のライブラリは、低レベル(人間に分かりにくく、コンピュータ寄りになっているが、制御は細かくできる)になっていますが、より分かりやすくするためにも色々なライブラリがあります。これらではコンピュータにコアがいくつ付いているかを自動的に判定し、それぞれのコアに処理を分けて振るようになっています。
  • OpenMP
  • MPI(Message Passing Interface)
  • TBB(Intel Threading Building Blocks)
  • Intel Click Plus
  • PPL(Microsoft Parallel Pattern Library)



また、今は同じ種類のCPUを使うホモジニアスマルチコアでなく、違う種類のCPUを使用するヘテロジニアスマルチコアという種類のマルチコアがあります。違う種類のCPUとしてよく使われるのがグラフィックを描画するコアであるGPU(Graphics Processing Unit)です。

現在のGPUは3D画像を扱うのが当たり前になっており、元々大量の3次元処理が得意と言うこともあり、画像処理だけでなく一般の計算処理にも使用しようとしたものです。GPGPU(General-purpose computing on graphics processing units)と言われます。

そのようなヘテロジニアスを使うライブラリとしては以下があります。
  • OpenCL
  • CUDA
  • OpenACC
  • C++AMP(C++ Accelerated Massive Parallelism)

今の所、ヘテロジニアスではメモリ空間が分離されており、スレッドというよりプロセス的な使い方が必要となっていますが、AMDが推進しているHMA(Heterogeneous System Architecture)のhUMAではメモリも統合されており、使い勝手も向上するのでは無いかと思います。


他にも一応、複数の演算を並行して行うという点では、SIMD演算と呼ばれるCPUの以下の命令も並列処理と言えるかもしれません。一つの命令で複数のデータの演算を行うことができます。
  • MMX, SSE, AVX(x86/x64)
  • NEON(ARMv7)
  • AltiVec, VMX(Power)
  • VIS(SPARC)
  • MIPS-3D, MDMX(MIPS)

CPUごとに命令が違うのですが、これらをC++上から同様に扱うMicrosoftのDirectXMachやlibsimdppなどのライブラリもあるようです。

上記のように並列処理には色々な実装があります。いつかは標準でもっと簡単に使えるようになって欲しいですが、現状ではOS標準のAPIの使用が一番いいかもしれません。


1 件のコメント:

  1. 私が神と非常に深く個人的な関係を持っていることは秘密ではありません。去年、ヘルペスと一緒に生活しなければならなかったすべてのでたらめを通してその関係を押して抵抗しましたが、神は私の頑固さよりも大きく、そのヘルペスと性器ヘルペスのすべてを突破しました。個人的には、自分がどれほど良くないかを何度も何度も聞くことは、可能な限り最悪の方法で私の心に侵入しました。私は完全にシャットダウンし、このように人生が一時的なヘルペスの大流行を終わらせようとしているように目を覚ましていました。
    漢方薬にノーと言うのはco病です。それは恐怖に基づいています。そして、それは私の心が望むものに対して不誠実です。自分の周りに壁を作らないでください。特に健康上の問題や治療法に関しては、ハーブが作られたり、大胆な一歩を踏み出すことを恐れています。多くの若い男性/女性が何度も何度も私に言います、イトゥア博士は私を詐欺するつもりです、しかし今日私は彼に試みを与えます私は誰も漢方薬について私を納得させないだろうと感じていますわずか2週間飲んで、私は1年と数ヶ月間住んでいますが、今はアウトブレイクを経験していません、ヘルペス、統合失調症、癌、脊柱症、線維筋痛症、フルオロキノロンなどの病気のために彼の漢方薬が必要な場合は彼に連絡できます毒性症候群線維異形成症進行性。致命的な家族性不眠症第5因子ライデン突然変異、てんかんデュピュイトラン病、線維形成、糖尿病、セリアック病、クロイツフェルト・ヤコブ、ライム病、てんかん、病気、Co病、コプシー、アルツ、骨異形成症男性/女性不妊症、腸疾患、ハンチントン病、糖尿病、子宮筋腫。疾患、ループス、リポイド蓄積症(ゴーシェ病)、多嚢胞性疾患、脳アミロイド血管障害、運動失調、肝硬変、関節炎、筋萎縮性側索硬化症、アルツハイマー病、副腎皮質癌。喘息、アレルギー、HIV、てんかん、不妊、。 Email..drituaherbalcenter@gmail.com then what app。+ 2348149277967 ....そこにいる病気の男性/女性に対する私のアドバイスは簡単です...常に開かれた本になり​​ましょう。あなた自身、あなたの状況、そしてあなたが何をしているのかについて正直にいじってください。何も遠慮しないでください。持ちこたえると、どこにも行けなくなります...たぶん、孤独なビルへの片道チケットであり、それはあなたが行きたい場所ではありません。だから私の最後の真実...そして私はこれを理解し始めたところです。

    返信削除