doridoridoriand’s diary

主に技術的なことを書いていく予定(たぶん)

2GBを超えるメモリを1つのプロセスで確保したい場合

C++で大量の変数を使って計算したい時に、たくさん変数確保するようなことがあるかと思います。 例えば以下のようなコードとか

const int REPEAT_TIMES = 10000;
double num_a[REPEAT_TIMES][REPEAT_TIMES], num_b[REPEAT_TIMES][REPEAT_TIMES], num_c[REPEAT_TIMES][REPEAT_TIMES];
double num_d[REPEAT_TIMES][REPEAT_TIMES], num_e[REPEAT_TIMES][REPEAT_TIMES], num_f[REPEAT_TIMES][REPEAT_TIMES];
double num_g[REPEAT_TIMES][REPEAT_TIMES], num_h[REPEAT_TIMES][REPEAT_TIMES], num_i[REPEAT_TIMES][REPEAT_TIMES];
double num_j[REPEAT_TIMES][REPEAT_TIMES], num_k[REPEAT_TIMES][REPEAT_TIMES], num_l[REPEAT_TIMES][REPEAT_TIMES];
double num_m[REPEAT_TIMES][REPEAT_TIMES], num_n[REPEAT_TIMES][REPEAT_TIMES], num_o[REPEAT_TIMES][REPEAT_TIMES];
double num_p[REPEAT_TIMES][REPEAT_TIMES], num_q[REPEAT_TIMES][REPEAT_TIMES], num_r[REPEAT_TIMES][REPEAT_TIMES];

まあウンコードなのは気にしないでくださいw
しかしこのようなコードを書いて、いざコンパイルしようとした時次のようなエラーが出てくることがあります

/tmp/ccEo3zaI.o: In function `_Z11calc_vectorv.omp_fn.2':
calcMultiThreadManyDataDouble.cpp:(.text+0x3ba): relocation truncated to fit: R_X86_64_32S against symbol `num_d' defined in .bss section in /tmp/ccEo3zaI.o
calcMultiThreadManyDataDouble.cpp:(.text+0x3e7): relocation truncated to fit: R_X86_64_32S against symbol `num_e' defined in .bss section in /tmp/ccEo3zaI.o
calcMultiThreadManyDataDouble.cpp:(.text+0x414): relocation truncated to fit: R_X86_64_32S against symbol `num_f' defined in .bss section in /tmp/ccEo3zaI.o
calcMultiThreadManyDataDouble.cpp:(.text+0x441): relocation truncated to fit: R_X86_64_32S against symbol `num_g' defined in .bss section in /tmp/ccEo3zaI.o
calcMultiThreadManyDataDouble.cpp:(.text+0x46e): relocation truncated to fit: R_X86_64_32S against symbol `num_h' defined in .bss section in /tmp/ccEo3zaI.o
calcMultiThreadManyDataDouble.cpp:(.text+0x49b): relocation truncated to fit: R_X86_64_32S against symbol `num_i' defined in .bss section in /tmp/ccEo3zaI.o
calcMultiThreadManyDataDouble.cpp:(.text+0x4c8): relocation truncated to fit: R_X86_64_32S against symbol `num_j' defined in .bss section in /tmp/ccEo3zaI.o
calcMultiThreadManyDataDouble.cpp:(.text+0x4f5): relocation truncated to fit: R_X86_64_32S against symbol `num_k' defined in .bss section in /tmp/ccEo3zaI.o
calcMultiThreadManyDataDouble.cpp:(.text+0x522): additional relocation overflows omitted from the output
collect2: ld returned 1 exit status

relocation truncated to fit

再配置はサイズに合うように切り捨てられました??ん??どういうことだ?

色々調べていたら次のサイトに巡りあうことが出来ました

シングルプロセッサプログラムの最適化

つまるところ、64ビットコンパイラでは、デフォルトでは32ビットと互換性を保つために2GB制限をかけているけど、コンパイルオプションを追加してやることで64ビットアプリケーションとしてコンパイル出来るようになるようです

よってコンパイル時に

$ g++ ファイル名.cpp -o 出力したい名前 -mcmodel=large

としてやればコンパイル出来るようになります。
実行結果を見てみると

この計算機はメモリが24GB積んであるのでこのプロセスだけで13.7GB程度消費しています。 …やばいな笑

CPUで並列処理

今回はGPU使ってないのでこの画像は適切じゃないんだけど、ちょっと適当なの探すのが面倒だったのでw

C++でThread並列を実装してみました。といっても大層なことをしたわけではなく、OpenMPを利用しただけです。 そもそもOPenMPってなんぞやってかんじですよね。すごい雑にいうと、マルチスレッド対応の実行ファイルを生成するのを補助してくれる業界規格です。この辺の詳しい事に関しては、以下の資料を読むとわかりやすかったです

利用方法はとても簡単で、omp.hヘッダファイルをインクルードした上で、#pragma`ではじまるディレクティブをforやwhileの前に書いてあげるだけです

といってもコードを書かないと何言ってるんだってなると思われるので、まずシングルスレッド版(並列化していないバージョン)を示します

#include <iostream>
#include <string>
#include <stdlib.h>

const int REPEAT_TIMES = 10000;
unsigned int num_a[REPEAT_TIMES][REPEAT_TIMES], num_b[REPEAT_TIMES][REPEAT_TIMES], num_c[REPEAT_TIMES][REPEAT_TIMES];

void initialize_params() {
  for (int counter = 0; counter < REPEAT_TIMES; counter++) {
    for (int counter2 = 0; counter2 < REPEAT_TIMES; counter2++) {
      num_a[counter][counter2] = 1 * counter2;
      num_b[counter][counter2] = 2 * counter;
    }
  }
}

int calc_vector() {
  for (int counter = 0; counter < REPEAT_TIMES; counter++) {
    for (int counter2 = 0; counter2 < REPEAT_TIMES; counter2++) {
      num_c[counter][counter2] = num_a[counter][counter2] + num_b[counter][counter2];
    }
  }
}

int main (int argc, char** argv) {
  double measure_start, measure_stop;

  measure_start = clock();
  for (int counter = 0; counter <= 100; counter++) {
    initialize_params();
    calc_vector();
  }
  measure_stop = clock();

  std::cout << "処理時間" << (double)(measure_stop - measure_start) / CLOCKS_PER_SEC << "[s]" << std::endl;

  return 0;
}

まあ至って単純。ただの足し算をひたすらしているだけです。これを並列化するためには #include <omp.h>を新たに付け足し、適切な箇所でディレクティブを追記してあげればOK。 すると次のようになるかと思われます

#include <iostream>
#include <string>
#include <stdlib.h>
#include <omp.h>

//OpenMPを用いたCPU並列化バージョン

using namespace std;

const int REPEAT_TIMES = 10000;
unsigned int num_a[REPEAT_TIMES][REPEAT_TIMES], num_b[REPEAT_TIMES][REPEAT_TIMES], num_c[REPEAT_TIMES][REPEAT_TIMES];

void initialize_params() {
  #pragma omp parallel for
  for (int counter = 0; counter < REPEAT_TIMES; counter++) {
    #pragma omp parallel for
    for (int counter2 = 0; counter2 < REPEAT_TIMES; counter2++) {
      num_a[counter][counter2] = 1 * counter2;
      num_b[counter][counter2] = 2 * counter;
    }
  }
}

int calc_vector() {
  #pragma omp parallel for
  for (int counter = 0; counter < REPEAT_TIMES; counter++) {
    #pragma omp parallel for
    for (int counter2 = 0; counter2 < REPEAT_TIMES; counter2++) {
      num_c[counter][counter2] = num_a[counter][counter2] + num_b[counter][counter2];
    }
  }
}

int main (int argc, char** argv) {
  time_t start_time, stop_time;

  time(&start_time);
  #pragma omp parallel for
  for (int counter = 0; counter <= 100; counter++) {
    initialize_params();
    calc_vector();
  }
  time(&stop_time);

  cout << "処理時間" << (double)(stop_time - start_time) << "[s]" << endl;

  return 0;
}

今回はCentOS上で実行するので、コンパイルオプションに-fopenmpをつけてコンパイルしました

$ g++ ファイル名.cpp -o 出力したい名前 -fopenmp

これでコンパイルし、MacBookPro上に立てたVagrantで実行しました。
結果は以下の表の通りになりました

シングルスレッド: 53.9202[s]
マルチスレッド(4スレッド): 16.0[s]

おー。大体3.3倍くらい早くなっていますね。4倍とはいかないものの、効果は絶大です

すべての処理を並列化出来るわけではないですが(時間発展するやつとかはそこまで早くならない)、積極的にマルチスレッドを活用していきたいと思いました

PlayFrameworkをちょっと触った

PlayFrameworkを触る機会があったので、自分の備忘録がてら書きます 以下の操作はUbuntu上で実行しています

皆さんは知っていると思いますが、PlayFrameworkはScala(と一部Java)で実装されています。よって利用するにはJavaが必要になります。Ubuntuインストール時のオプションにも依りますが、Javaが入っていないこともあるので、まずはJavaをインストールします

PlayFrameworkの最新バージョンは、この記事を書いた時には2.4.2だったので、必要なJDKのバージョンは8でした。よってJDK8を入れます

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer
$ sudo apt-get install oracle-java8-set-default

次にPlayFramework本体をダウンロードしてきます。今回は本家のサイトからダウンロードしてくる形にしました

$ cd $HOME
$ wget "https://downloads.typesafe.com/typesafe-activator/1.3.5/typesafe-activator-1.3.5.zip"
$ unzip typesafe-activator-1.3.5.zip

実行ファイルに対してパスが通っていないので、環境変数に追記

export PATH=/home/ユーザー名/activator-dist-1.3.5/activator:$PATH

動作チェックを兼ねて、以下のコマンドで新規にPlayアプリケーションを作成します

$ activator new SampleApp

するとSampleAppというフォルダが出来たと思うの、移動して、以下のコマンドで実行します。初回起動はBuildが走るため、ちょっと時間がかかります

$ activator run

ブラウザからlocalhots:9000にアクセスして、Your new application is readyと表示されれば正常に稼働しています

雑記

いやー全然書いてねえなw
帰省している間はほとんどパソコンを開かなかったので、結局何も進みませんでしたとさ。まあ休みに行っているからそれでいいんだけど

OpenMPで並列化しようとしてうまく行かなかったりRubyの並列処理書き途中だったりして、色々と中途半端だったのですが、それよりも自分の部屋が最悪に汚くなって、完全に作業効率落ちていたので、今日はちょっと重めに掃除しました(大掃除ではないけど、いつもの掃除機かけるだけよりは上な感じで)

まずたくさん積まれた酒瓶を片付け、ビールの空き缶を片付け、ミネラルウォーターのペットボトルを捨てたらエライ綺麗になりました。つーかこまめに捨てよろって話ですね。すみません

さらにもう使わないであろう教科書やプリント類を捨てたり、ダンボールにまとめたら山みたいなのが消失しました。もっと早くにやればよかった。。。

僕の部屋はまだまだものが多いのですが、まあ少しずつ捨てるなり譲るなりしないとなと思っています。どうせ来年には引っ越すわけだし

というわけで久しぶりの更新でした

雑記

本当はRubyで並列計算に関してやろうと思ったのですが、Ruby標準のThreadクラスを使用して実装しようとしたところ思ったより手こずってしまったので、もうちょっと調査をしてから掲載します
なかなか面倒くさい。。。

僕は今帰省中なので普通に更新頻度が落ちます。更新しても雑記ばかりになることが予想されますので皆さんあまり期待しないでくださいw

まあだれも見てないかw

雑記

だれかNginxのリバースプロキシの設定教えてくれー。うごかんー

さて本当は色々書きたいことあるんだけど、記事にするにはまだ調査不足だし中途半端だしのネタを箇条書きにしてある(だけ)のエントリーだよ(ぉ

  • GIL(Global Intterspreter Lock)について色々な言語の対応状況まとめる
  • Cocos2dやりだしたし、せっかくならC++をもっとやりこもう。最近のC++すごいモダンだし(小並感)
  • HUGOのテーマ作る
  • サーバーサイドJavaScriptと真剣に向き合う
  • 無線LANルーター壊れかけたので、新しいの欲しい
  • PHPバージョン7ってなんぞや5.6(最新の安定版)より2倍早いらしいけど本当??
  • CUDA ああCUDA
  • 広角の一眼のレンズ欲しい。何がいいかしら

・・・

それにしても暑いですね。僕は完全に夏バテしてしまいました。あと寝不足。あつい 個人的に静的サイトブームなので、ある程度までならCMSいらない感じのHUGOテーマつくろうって思っている(行動しているわけではない)

と言いつつ最近サーバーサイドで書いているのはSinatraですが。。。