So Chigusa

並列計算

for ループで同じ処理を何度も繰り返す場合

シェルスクリプト + c++ のコスパがもっとも良いという結論に至った。 ループが進むごとに異なる入力パラメータを用いて計算したい状況を考慮して、まず以下の下準備をする。

実行オプションの設定(boost を使用)

#include <boost/program_options.hpp>

using namespace boost::program_options;

int main(const int arg_c, const char *const *const arg_v)
{
  // define options
  options_description description("option");
  description.add_options()
  ("option1", "description of option 1")
  ("option2,o", value<int>()->default_value(0), "description of option 2")
  ...
  ("help,h","help");

  // extract option values
  variables_map vm;
  store(parse_command_line(arg_c, arg_v, description), vm);
  notify(vm);

  if (vm.count("help"))
  {
    std::cout << description << std::endl;
    return 1;
  }
  if (vm.count("option1"))
  {
    ...
  }
  auto o2 = vm["option2"].as<int>();
  ...

  return 0;
}

などと書いておけば、 ./a.out --help

option:
  --option1                  description of option 1
  -o [ --option2 ] arg (=0)  description of option 2
  -h [ --help ]            help

とヘルプが参照でき、実行時は ./a.out --option1 -o 1 のようにできる。

実行オプションを変えつつ複数スレッドで実行

ここからがシェルスクリプトの出番。例えば 4スレッド使ってオプション -o に 0~99 の値を渡す計算をしたい場合には、

#!/bin/bash
if [ $1 -eq 1 ]; then
  LB=0
  UB=24
elif [ $1 -eq 2 ]; then
  LB=25
  UB=49
elif [ $1 -eq 3 ]; then
  LB=50
  UB=74
elif [ $1 -eq 4 ]; then
  LB=75
  UB=99
fi

for o in $(seq $LB $UB)
do
  ./a.out -o $o
done

と書いた run_all.sh を作っておく。 $1 はシェルスクリプトに渡された 1つ目の引数なので、1~4 の引数を渡して run_all.sh を 4つ同時に走らせれば並列計算が可能。 これを簡単にやるには

seq 4 | xargs -P 4 -I{} sh ./run_all.sh {}

とすれば良い。

もう少し複雑な計算の場合

まだこれが必要な状況に当たったことがないが、c++ 内部でスレッドを複数立てて完結させることも当然できる。 std::thread を使うと良いらしい。 そういえば15年くらい前に猫でもわかるプログラミングシリーズで C言語を勉強していた際、なんの役に立つかもわからずにスレッドを複数使って丸をたくさん描くコードを書いたなぁと思い出すなど。多分この回だね。