Matlab

MATLABで簡単かつ汎用性の高い並列処理を考える

結論:forループを分割し複数ウィンドウ立ち上げて並列処理→高速化

次のコードを導入し、複数ウィンドウ立ち上げて並列化することが、手間を最小限に抑え簡単に並列処理を実現する方法ではないか、という提案です。

簡単にコスパ良く並列化したい方の参考になるのではと思います。

導入に至る背景は本投稿をお読み頂ければ分かると思います。

id = ***;
loop_num = ***;
parallel_num = ***;

for idx = round(loop_num/parallel_num)*(id-1) + 1:round(loop_num/parallel_num)*(id)
    fprintf("idx:%d \n", idx);
end

MATLABで高速化したくなる状況

MATLABで数値計算をがっつり行うときは、Python等に比べれば多少は計算速度の早いMATLABでも時間がかかることがよくあります。

僕は基本的に複雑なコードを書くことは少ないですが、もう少し早く終わらせたいなという場面は割と遭遇します。

特によくあるのが、単純に計算を進めると次の日の夕方まで計算がかかってしまい一日ロスがでてしまうので、明日の朝までに計算を終えたいなという状況です。

MATLABの処理負荷

あくまで僕の場合ではありますが、MATLABで計算をしている際のPCの計算負荷状況は、次のようなケースが多いです。

  • CPU使用率:10〜20%
  • メモリ専有量:1〜2GB程度

MATLABを一つ立ち上げた状態だと、CPUもフルに使えていない状況が多いです。

PCのスペックにもよりますが、上の例だと4〜5倍くらいの計算の余力があることが分かります。

処理能力があるのに休んているCPUがいるのは非常にもったいないですね。

できればCPUをフルに稼働させ、計算を早く終わらせたいところです。
(そして願わくば早く研究や仕事を終わらせて家に帰りたいですよね。)

一般的なMATLABの並列処理について

MathWorksが紹介する並列処理の解決方法

MathWorksの並列処理の解決方法の選択のページでは、いろいろな並列化する方法が紹介されています。今回は扱いませんが、簡単に紹介します。

Mathworks : 並列計算の解決策の選択

解決法 必要な製品
データをより高速で処理する必要があるか コードをプロファイリングする。 MATLAB
コードをベクトル化する。 MATLAB
MathWorks 製品の自動並列計算サポートを使用する。 MATLAB Parallel Computing Toolbox
GPU がある場合は、gpuArray を試す。 MATLAB Parallel Computing Toolbox
parfor を使用する。 MATLAB Parallel Computing Toolbox
処理を高速化する他の方法を探しているか parfeval を試す。 MATLAB Parallel Computing Toolbox
spmd を試す。 MATLAB Parallel Computing Toolbox
以下省略

コードのプロファイリングやベクトル化は基本的な対処としてありますが、それ以上の対策は、追加のアドオン(MATLAB Parallel Computing Toolbox)が必要になります。

これらを使いこなせれば、もちろん効果を発揮すると思いますが、次のようなデメリットのあると考えています。

上で紹介される方法のデメリット

  • 有料
  • GPU活用による対策:早くはなるが、GPUに組込むための複雑なコードの追記が必要となることが多い
  • parforによる対策:導入は簡単だが、ループ内で扱える演算が限定され、複雑な処理を並列化しようとするとうまく導入できない

以上から、有料であることに加え、高速化するために、追加の労力がかかってしまいがちです。

計算結果を得るまでの時間を短くしたいのに、追加の労力がかかると本末転倒ですよね。

一番単純な並列化は、MATLABを複数ウィンドウ立ち上げること

あまり知らない方も多いかもしれませんが、MATLABは複数個ウィンドウを立ち上げてそれぞれで計算させることができます

通常のライセンスがあれば、どなたでもできると思います。

ということで、今回は、上で紹介した高級な並列化ツールを使わずに、複数のMATLABウィンドウを立ち上げて並列処理していく方法を考えたいと思います。

非常にアナログなやり方ですが興味がある方は引き続きお付き合いください。

並列化できる箇所

最初に並列処理は真面目に検討すると非常に奥の深い分野です。

真面目に考えると、CPUが複数ある際には、次のような複雑なCPUやスレッド間の同期処理を考慮していく必要がでてきます。

  • 各CPU毎に何を処理させるか
  • 各CPU間の同期
  • 各スレッドに何をどの順番で処理させるか... etc

とはいえ、一般的に扱う処理はそこまで複雑なものばかりではありません。

単純な並列化の方法を知っていれば解決できる場面が非常に多いのではないかと思います。

並列化には色々なアプローチがある中で、最も簡単で単純な方法は、forループに手を入れることだと思います。

今回やりたい超簡単な並列処理

次を満たす並列処理を目指します。

  • 追加ToolBoxを使わない
  • GPUも使わない
  • forループを分割するのみ

MATLABで簡単かつ汎用性の高い並列処理の具体例

今回は次のコードを並列化していくことを考えたいと思います。

Step0:並列化するコード例

% ---------- Step0 ----------
for idx = 1:100
    fprintf("idx:%d \n", idx);
end

forループ内で、何番目のループ化を表示するだけの簡単なプログラムですが、この部分に本来やりたい複雑な計算が入っていることを想定しています。

 

ではどのように分割して並列処理していくかを見ていきましょう。

今回は、1〜100までを順番に計算するループ処理になっています。

Step1:2つに分割してみる

% ---------- Step1 ----------
%1つ目のファイル
for idx = 1:50
    fprintf("idx:%d \n", idx);
end

%2つ目のファイル
for idx = 51:100
    fprintf("idx:%d \n", idx);
end

Step1のように1〜50を1人目(1つ目のMATLABウィンドウ)に51〜100を2人目(2つ目のMATLABウィンドウ)に割り当てて計算すれば、半分の時間で終わりそうですね。

※ただし、ループ間のデータの依存関係はないという前提になります。例えば、1ループ目の結果を2ループ目で再利用する場合は、このような単純な残念ながら分割はできません。m(_ _)m

結局並列化でやりたいことは、「処理を分割して複数人で対処したら、人数の数だけ速くできる」ということです。

 

ただこのコードは、2つのファイルに跨って冗長な記述が沢山あり、あまりよろしくないです。

例えば、ループ回数やループの中身を変更する際は、1つ目と2つ目の両方のファイルを書き換える必要が出てしまいます。

同じ変更を2回やる必要の出る冗長コードは、あまりかっこよくないですね。

人の手で書き換える箇所が増えるとそれだけ、意図しないエラーに繋がります。

このため、これらのコードをもう少し一般的に、共通項を増やして行きましょう。

Step2:共通化を目指す

% ---------- Step2 ----------
%個別part (1つ目のファイル)
id=1;

%共通part
if id==1
    for idx = 1:50
        fprintf("idx:%d \n", idx);
    end
elseif id==2
    for idx = 51:100
        fprintf("idx:%d \n", idx);
    end
end

次のコードを1つ目のMATLABウィンドウで処理します。

% ---------- Step2 ----------
個別part (2つ目のファイル)
id=2;

%共通part
if id==1
    for idx = 1:50
        fprintf("idx:%d \n", idx);
    end
elseif id==2
    for idx = 51:100
        fprintf("idx:%d \n", idx);
    end
end

次のコードを2つ目のMATLABウィンドウで処理します。

 

Step2では、if文でコードを分岐させました。

idは、分割された処理のうち、何番目の処理を担当するかを表します。

まだ見た目は複雑かもしれませんが、if文のブロックを一度作成した後に、もう片方のファイルにコピーし、idのみ変更する、という操作とするだけでも、ヒューマンエラーを格段に下げられそうです。

Step3:ループ回数を共通化する

% ---------- Step3 ----------
%個別part (1つ目のファイル)
id=1;
loop_num = 100;

%共通part
if id==1
    for idx = 1:round(loop_num/2)
        fprintf("idx:%d \n", idx);
    end
elseif id==2
    for idx = round(loop_num/2) + 1 :loop_num
        fprintf("idx:%d \n", idx);
    end
end

Step3では、ループ回数をloop_numとして共通で扱えるようにしました。

Step4:並列数も共通化する

% ---------- Step4 ----------
%個別part (1つ目のファイル)
id=1;
loop_num = 100;
parallel_num =2;

%共通part
if id==1
    for idx = 1:round(loop_num/parallel_num)
        fprintf("idx:%d \n", idx);
    end
elseif id==2
    for idx = round(loop_num/parallel_num) + 1 :loop_num
        fprintf("idx:%d \n", idx);
    end
end

Step4では、並列数もparallel_numとして共通で扱えるようにしました。

並列数は、元の処理をいくつに分割かという意味です。

例えば、parallel_num=5の場合は、5分割した処理を、5つのウィンドウそれぞれで計算することを意味します。

Step5:idを共通化するための準備

% ---------- Step5 ----------
%個別part (1つ目のファイル)
id=1;
loop_num = 100;
parallel_num =2;

%共通part
if id==1
    for idx = round(loop_num/parallel_num)*0 + 1:round(loop_num/parallel_num)*1
        fprintf("idx:%d \n", idx);
    end
elseif id==2
    for idx = round(loop_num/parallel_num)*1 + 1:round(loop_num/parallel_num)*2
        fprintf("idx:%d \n", idx);
    end
end

Step5では、更に一般的に表すために、 forループの範囲の記述方法を書き換えました。

×(かける)ゼロ等の表現に変えていますが、計算自体は同じです。

Step6:idを共通化する

% ---------- Step6 ----------
%個別part (1つ目のファイル)
id=1;
loop_num = 100;
parallel_num =2;

%共通part
for idx = round(loop_num/parallel_num)*(id-1) + 1:round(loop_num/parallel_num)*(id)
    fprintf("idx:%d \n", idx);
end

最後にStep6では、Step5をベースにして、id毎に分けていたforループを共通化しました。

ここまで、共通化し、並列処理をしてやれば、id, loop_num, parallel_numを指定するだけで、複数のMATLABウィンドウにまとめて指示することができます。

これで大分使い勝手がよくなったのではないでしょうか。

以上、MATLABで簡単かつ汎用性の高い並列処理に関する紹介でした。

 

MATLAB関連記事のリンク

MATLAB関連記事のリンクを掲載しておきます。
これらの記事も早く研究やお仕事を終わらせるためのヒントになるかもしれません。ご参考まで。

・MATLABでファイル名一覧を取得する方法
MATLABでファイル名一覧を取得する方法(cell配列)

・MATLAB関数の引数を究極的に単純化する↓
MATLABで引数に何でも入れられる柔軟な関数を定義する

-Matlab

© 2024 Gohey Blog