FreeBSD サーバと Moodle による Web 学習環境

これをやるきっかけは, 大学の1年生が主に使っているシステム (がマウントしているファイルサーバだったかな) が今年度でサポート完全停止でいつ壊れても修理できないそうで, 今学内全体で使うためのクラウドシステムの準備中らしく, その期間安全に使えるシステムがないとかなんとかで, 出席管理が欲しいとかそういう理由だったと思います.

FreeBSD は別にいいんですが, Moodle は説明します.

Moodleとは - MoodleDocs

"Moodleはインターネット上で授業用のWebページを作るためのソフトです。
教育学でいう社会的構築主義の考え方に基づいて作られており,日々改良が行われています。"

とのことで, 簡単に言うと e ラーニングシステムみたいなものだと思います.

先生にその名前を聞くまで知らなかったのですが, セットアップして使えるようになるまで忘れてました. 僕も微積分の講義で使ってました. というかデザインを見て思い出したので, 「あああれは Moodle だったのか」という感じです.

Moodle の設定は上のページのように日本語で公式のドキュメントもあるので (若干ふるい気がする) そこまで難しくないと思います.

必要なのは SQL サーバの設定と, PHP の設定, Apache の設定なので.

FreeBSD はサーバをたてるかって時に「Ubuntu はサーバとして信用ならない」という先生が居たので FreeBSD にしました. あと Ports に Moodle も入ってたからです.

インストール自体は公式のドキュメント Moodleをインストールする - MoodleDocs を見れば大体できます.

問題は, Ports でインストールするとディレクトリが /usr/local/www/moodle, /usr/local/www/moodledata になると思います.

Apache のドキュメントルートは /usr/local/www/apache22/data 辺りになるので, 修正したいのですが, ドキュメントルート自体はそのままで, /moodle とウェブでアクセスするときは /usr/local/www/moodle を使用するようにしたい.

それ自体は簡単で Apache の Alias モジュールを使えば解決.

mod_alias - Apache HTTP Server

なのでかなり簡単です.

問題は, 僕にウェブの知識がほとんどないので詰んでる.

既に設定は全て終わってるので, 後は実際に使う先生方に Moodle の管理者権限を渡せば完了.

Boost.MPI は実際 MPI_Allgatherv や MPI_Scatterv をサポートできてない

Tutorial - 1.53.0

Boost.MPI ドキュメントの "Table 19.5. Collectives" を見てもらえると分かりますが, Boost.MPI では MPI_Allgatherv といった collectives はそれぞれ非 v な collectives でサポートされているかのように書かれています.

ちなみに MPI_Allgatherv といった collectives に v が付いたものは, 各ランクに送受信するデータをランクごとに可変にすることを許可した API です.

boost::mpi::allgather を使って頂ければ分かると思いますが, 実際には意図した通りには動かず, SEGV で落ちると思います. そもそも, Boost.MPI では allgather に MPI_Allgather を使用しているため, MPI_Allgatherv のようにランクごとに可変長のデータは渡せないはずなのです.

それもあって, Boost.MPI には gatherv, scatterv を用意するチケットが Boost 1.45.0 から投げられています.

boost::mpi::scatterv and boost::mpi::gatherv added : https://svn.boost.org/trac/boost/ticket/5292

チケットを発行した Júlio Hoffimann 曰く, 「どうも実装が Douglas Gregor には気に入られてないないようだ」とのこと.

なのでランクごとに計算するサイズが違う場合 (僕が体験した中ではほとんどがそうですが), isend, irecv を使って上手いこと実装するか, 実装してパッチを投げるかをしないと駄目ですね.

Boost.MPI, Serializationで動的確保した配列がメンバに含まれるクラスインスタンスをシリアライズして送る.

できるのかと思ってたんですが, Boost.Serialization のチュートリアルちゃんと読んだら書いてありました.

Serialization

配列のサイズが分かっていればできそうです.

(2013/02/11 追記) 動的確保されたメモリの場合, serialize 関数でメモリ確保は行われないため, 既に確保されている必要があります. そのため, save/load を定義して load で動的メモリ確保を行う必要があります.

#include <boost/mpi.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/split_free.hpp>
#include <gmp.h>

// MPI で送信するデータ
struct Hoge
{
  int size;
  double * arr;

  Hoge() : size(), arr() {}

  Hoge(int size_) : size(size_), arr() {
    arr = new double[size];
  }

  ~Hoge() {
    delete arr;
  }
};

namespace boost { namespace serialization {

// 非侵入型シリアライズ
template<class Archive>
void serialize(Archive & ar, Hoge & h, const unsigned int version)
{
  // 非侵入型の save/load を呼び出す.
  split_free(ar, h, version);
}

template<class Archive>
void save(Archive & ar, Hoge const& h, const unsigned int)
{
  ar & h.size;
  for(int i = 0 ; i < h.size ; ++i)
    ar & h.arr[i];
}

template<class Archive>
void load(Archive & ar, Hoge & h, const unsigned int)
{
  ar & h.size;
  // 本当はここで既に確保されたかや確保方法を合わせるなどが必要. (new, malloc...)
  h.arr = new double[h.size];
  for(int i = 0 ; i < h.size ; ++i)
    ar & h.arr[i];
}

} } // namespace boost::serialization

int main(int argc, char** argv)
{
  boost::mpi::environment env(argc, argv);
  boost::mpi::communicator world;

  Hoge h;

  std::srand(std::time(0));
  if(world.rank() == 0) {
    h.size = 2;
    h.arr = new double[h.size];
    for(int i = 0 ; i < h.size ; ++i) {
      h.arr[i] = double(std::rand()) / RAND_MAX;
    }
  }

  boost::mpi::broadcast(world, h, 0);

  for(int i = 0 ; i < h.size ; ++i) {
    printf("[%d] arr[%d] = %f\n", world.rank(), i, h.arr[i]);
  }
}

void ポインタのようなものにはもちろんこの書き方では使えません.

Boost.Serialization は非侵入型のシリアライズもできるので既に定義されている型にも適用できます, つまりサードパーティ製ライブラリにある型でも送信できるという訳です. もちろん全てではなくて, データ構造が分かっているものなど限定はされますが.

Boost.MPI, Boost.Serialization のおかげで結構便利ですね.

Intel C++ Compiler で C++ 標準ライブラリを使うときに気をつけたいこと

2ヶ月以上ぶりです.

最近 Intel C++ Compiler (icpc) を使う機会が増えたのですが, GCC で書いたプログラムを他のコンピュータ (自分でシステム管理ができないタイプ) でコンパイル/実行しようとしたときに躓いたのでメモ.

Intel C++ Compiler Linux 版は GCC とのバイナリ互換性を持ってます. それは GCC をバックエンドコンパイラとして使用する必要があるからか, 使用しているからなのかは僕は詳しく知りませんが...

なので, STL などの標準ライブラリは GCC のヘッダをそのまま使っているようです. そのため, 今使っている GCC が古いと問題にある場合があります. 例えば move semantics とかですね. rvalue reference があっても STL などのコンテナが対応していないのでは問題になります.

僕が特に躓いたのは関数の std::begin/std::end が になかったことです. なんかおかしいなと思って -H オプションでどのヘッダを読み込んでいるのか確認しました.

GCC は 4.4.5 で Intel C++ Compiler は 13.1 です.

#include <vector>

int main(int argc, char** argv) {}
$ icpc -H main.cpp
. /usr/include/c++/4.4.5/vector
.. /usr/include/c++/4.4.5/bits/stl_algobase.h
... /usr/include/c++/4.4.5/x86_64-redhat-linux/bits/c++config.h
.... /usr/include/bits/wordsize.h
.... /usr/include/c++/4.4.5/x86_64-redhat-linux/bits/os_defines.h
..... /usr/include/features.h
...... /usr/include/sys/cdefs.h
....... /usr/include/bits/wordsize.h
...... /usr/include/gnu/stubs.h
....... /usr/include/bits/wordsize.h
....... /usr/include/gnu/stubs-64.h
.... /usr/include/c++/4.4.5/x86_64-redhat-linux/bits/cpu_defines.h
... /usr/include/c++/4.4.5/cstddef
.... /opt/intel/Compiler/Intel/composer_xe_2013.1.117/compiler/include/stddef.h
..... /usr/lib/gcc/x86_64-redhat-linux/4.4.5/include/stddef.h
... /usr/include/c++/4.4.5/bits/functexcept.h
.... /usr/include/c++/4.4.5/exception_defines.h
... /usr/include/c++/4.4.5/bits/cpp_type_traits.h
... /usr/include/c++/4.4.5/ext/type_traits.h
... /usr/include/c++/4.4.5/ext/numeric_traits.h
... /usr/include/c++/4.4.5/bits/stl_pair.h
.... /usr/include/c++/4.4.5/bits/move.h
..... /usr/include/c++/4.4.5/cstddef
...... /opt/intel/Compiler/Intel/composer_xe_2013.1.117/compiler/include/stddef.h
....... /usr/lib/gcc/x86_64-redhat-linux/4.4.5/include/stddef.h
..... /usr/include/c++/4.4.5/bits/concept_check.h
... /usr/include/c++/4.4.5/bits/stl_iterator_base_types.h
.... /usr/include/c++/4.4.5/cstddef
..... /opt/intel/Compiler/Intel/composer_xe_2013.1.117/compiler/include/stddef.h
...... /usr/lib/gcc/x86_64-redhat-linux/4.4.5/include/stddef.h
... /usr/include/c++/4.4.5/bits/stl_iterator_base_funcs.h
... /usr/include/c++/4.4.5/bits/stl_iterator.h
... /usr/include/c++/4.4.5/debug/debug.h
.. /usr/include/c++/4.4.5/bits/allocator.h
... /usr/include/c++/4.4.5/x86_64-redhat-linux/bits/c++allocator.h
.... /usr/include/c++/4.4.5/ext/new_allocator.h
..... /usr/include/c++/4.4.5/new
...... /usr/include/c++/4.4.5/cstddef
....... /opt/intel/Compiler/Intel/composer_xe_2013.1.117/compiler/include/stddef.h
........ /usr/lib/gcc/x86_64-redhat-linux/4.4.5/include/stddef.h
...... /usr/include/c++/4.4.5/exception
.. /usr/include/c++/4.4.5/bits/stl_construct.h
.. /usr/include/c++/4.4.5/bits/stl_uninitialized.h
.. /usr/include/c++/4.4.5/bits/stl_vector.h
... /usr/include/c++/4.4.5/initializer_list
.. /usr/include/c++/4.4.5/bits/stl_bvector.h
.. /usr/include/c++/4.4.5/bits/vector.tcc

こんな感じで STLGCC のものを直接使ってるようです.

ちょっとこれだと困ったので, 管理者にせめて 4.6.3 辺りにバージョンアップしてくれないか相談してみます.

C++ Advent Calendar 2012 「18日 : Cer に知って欲しい C++」

この記事は, C++ Advent Calendar 2012 (C++ Advent Calendar 2012 - PARTAKE) の18日目です.

温いネタをやりたいので C 言語を使っている人 (Cer) に C++ の知って欲しい/今すぐ使える機能を Tips 的に書いていこうと思います.

対象は特に設けなくていい気がしますが, 例えば数値計算クラスタとか. 普段 malloc とか for とかぶんぶんしてるような気がするので言い例かも.

よく分からないところがあったらこの記事か Twitter の @krustf にでも質問してください.

後, 詳しい説明はしないほうが良いと思います. "C++ ってこんな風にかけるのか!" ぐらいの感想を持って頂いて, 使ってみようとする人が増えてくれればと思います. その点では途中よく分からない語が出てくるかもしれませんが「へー」ぐらいで見逃してください.

注意

書かれている全てのコードは GCC 4.7.1 でビルドおよび動作を確認しています.
あとここで言っている C++C++11 のことです. 天変地異が起こったとしても間違っても C++03 のことではありません.

以下のようにコンパイルオプションを付けてビルドします.

% g++ -std=c++11 -pedantic-errors main.cpp

Case 1 : malloc/free しないで動的な連続領域を確保する.

よくあるもので「特に」発狂するのが malloc/free で, 大体メモリの free し忘れがある. そもそも free を手動でやって, 「あれ? free したっけ?」は必ずあるし, SAFE_FREE(ptr) とか SAFE_DELETE(ptr) とかいう悪しき関数マクロを作っていた人も多いはず.

C++ ならそもそも malloc/free に頼らず, メモリ上で連続な配列を動的確保できる. それが std::vector.

#include <vector> // std::vector

int main() {
  std::vector<int> v(100, 0);
}

std::vector の T には配列の要素の型を書く. 上記の例の場合, C 言語で書くと以下のようなものと同じ.

void main() {
  int* v = malloc(sizeof(int) * 100);
  for(int i = 0 ; i < 100 ; v[i++] = 0); // あえて memset 使ってない.
  free(v);
}

std::vector では, 作成時に配列の初期要素数と, 初期化時の値が渡せる. 何も渡さないで, 空の動的配列を作ることもできる.

後で要素を追加するには push_back メンバ関数がある. また size メンバ関数で配列の現在の要素数が分かる. C の配列と同じように [] でアクセスすることもできる.

std::vector<int> v;

assert(v.size() == 0);
for(int i = 1 ; i <= 5 ; ++i) {
  v.push_back(i);
}
assert(v.size() == 5);

int sum = 0;
for(std::size_t i = 0 ; i < v.size() ; ++i) { // v.size() の戻り値は通常 size_t
  sum += v[i];
}
assert(sum == 15);

実行結果 : Ideone.com - lYLsbp - Online C++0x Compiler & Debugging Tool

push_back を用いず, std::vector の作成時に配列サイズを渡しておけばこれまでの C の配列のように扱うことができるので非常に便利.

C++ の規格的にも std::vector の配列の要素は連続していることが保障される. 先頭要素へのポインタが欲しい場合には data メンバ関数を使えば取れる.

Case 2 : 単純な計算で for をまわさない

例えば, とりあえず配列に入っている値の総和が欲しかったり, 全部を n 倍したいとかあったとき, C 言語ではいちいち for 文を書く必要がある *1.

int sum(int* arr, int size) {
  int s = 0;
  int i;
  for(i = 0 ; i < size ; ++i) {
    s += arr[i];
  }
  return s;
}

void main() {
  int arr[5] = { 1, 2, 3, 4, 5 }
  assert( sum(arr, 5) == 15 );
}

C++ なら std::accumulate がある.

#include <array>   // std::array
#include <numeric> // std::accumulate
#include <cassert> // assert

int main() {
  std::array<int, 5> arr = { 1, 2, 3, 4, 5 }; // C の配列と同じ
  assert( std::accumulate(std::begin(arr), std::end(arr), 0) == 15 );
}

std::begin, std::end はそれぞれ配列の先頭要素と末尾の次の要素 (上の例で言うと"5"の次のアドレス) , と今は考えておくといいだろう.

for 文が要らなくなってコードが短くなりバグの発生確率も減った*2. 素晴らしい.

他にもヘッダは異なるが, には安定/非安定ソートや, 条件にマッチした要素を探し出す find, 配列のコピーを行う copy などの関数が用意されている. 詳しくは algorithm - cpprefjp - C++ Library Reference を見るといいだろう.

Case 3 : リソース管理

我々は Cer ないし C++er である. である以上リソース ( memory, file I/O, graphics... ) の確保と解放の面倒を見なければいけない.

しかし, リソースの管理はめんどくさい. 恐らく大体の場合, 大量の動的確保メモリやグラフィックスハンドルやその他コンテキストが山のようにあり, それら全てを安全に削除する必要がある. GC がある言語がなんとうらやましいことか.

C++ にはそれらリソースの管理(特に解放処理)についてスマートポインタを用意している. スマートポインタはクラスであり, ポインタを模倣するように作られている. 複数のスマートポインタがあり, またそれぞれ違う用途で用意されているが, 基本的には標準ライブラリにある shared_ptr と unique_ptr が最も使われるだろう.

#include <memory> // std::shared_ptr

std::shared_ptr<int> global;

int main() {
  void * lp = 0;
  {
    std::shared_ptr<int> local = std::make_shared<int>(42);
    assert(*local == 42);
    global = local;
    lp = local.get(); // 実際のポインタは get で取得できる.
  }
  void * gp = global.get();
  assert(lp == gp); // ポインタが等値 == 同一アドレス
}

// プログラム終了時に global のみが所有するため global がリソースを削除.

※簡単なサンプルが思い浮かばなかった... ごめんなさい.

std::shared_ptr はリソースを共有するためのスマートポインタで, 複数箇所で同一リソースを参照したときに, 「全ての参照が外れたらその段階でリソースを削除する」という動作をする.
例えば, 音楽ファイルの PCM データをメモリに展開したときに shared_ptr で保持すれば, 複数のサウンドデバイスで共有することができる. *3

しかし, std::shared_ptr ではもったいないと思う時があるだろう, 例えば関数のローカルスコープ内でのみ使用するリソースなら, 単に自動的にリソースを解放してくれれば良い. そういう時は std::unique_ptr が妥当だろう.

#include <memory> // std::unique_ptr

int main() {
  std::unique_ptr<int> ptr(new int(42));
  assert(*ptr == 42);
  // スコープの終わりで自動的に削除
}

かなり単純なサンプルだが, 大体分かると思う. * でポインタと同じようにポインタの中身を見ることができるし, shared_ptr と同じく get で実際のポインタを取り出せる.

例えばこれは, FILE などで使いたくなるが, 若干手順を踏む必要がある. *4

#include <memory> // std::unique_ptr
#include <cstdio> // std::FILE
#include <cassert>

int main() {
  std::unique_ptr<FILE, decltype(&std::fclose)> file_ptr(std::fopen("hoge.dat", "w"), std::fclose);

  if(file_ptr) // 有効なポインタが入っている場合 true と評価される. shared_ptr でも同じ.
    std::fprintf(file_ptr.get(), "hello, world");

  // スコープの終わりで自動的に std::fclose が呼ばれる.
}

カスタムデリータという機能を用いて std::fclose を file_ptr で確保したリソースの削除を行う関数として登録している. (unique_ptr のデリータ指定 - krustf の雑記)

shared_ptr にもあるので, これを使えば通常の free や delete といった関数以外で削除しなければならないリソースも shared_ptr, unique_ptr を用いて管理できる.

もちろん, 生のポインタを使うべき箇所 (ライブラリ内部など) もあると思うが, 基本的にはこれらを用いて自動で解放することが, 人道的に最も良い解決策である.

Other : Boost C++ Libraries

C++ の(恐らく)最も代表するべきライブラリに Boost がある.

Boost C++ Libraries

この C++ Advent Calendar でも恐らく幾度となく紹介されたと思う.

実は今回紹介した shared_ptr も, 元々は Boost で実装されていたライブラリである. 他にもスレッドライブラリや, ハッシュコンテナ, 乱数生成など実に多くのライブラリが Boost から C++ 標準ライブラリに移植されている.

標準ライブラリにはないが, Boost にはもっと有用なライブラリが沢山用意されているので, 一度上の公式ページからドキュメントを読んでみるといいだろう.

ちなみに私が最近よく使っているのは,

  1. Chapter 6. Boost.Container - 1.52.0
  2. Chapter 15. Boost.Lexical_Cast 1.0 - 1.52.0
  3. Chapter 17. Boost.MPI - 1.52.0
  4. Chapter 1. Phoenix 3.0 - 1.52.0
  5. Chapter 1. Range 2.0
  6. Boost Test Library

辺りか.

数時間前に言っていた多次元配列も完全にメモリが連続なものは Boost.MultiArray で作れる. (MultiDimensional Array Libary - 1.52.0)

終わりに

今回は緩めでした. (というか時間ないやばい)

僕が C を使っている人々に特に薦めたいのは最初の Case 1 で紹介した vector です. 配列を動的に確保しなければいけないのはどうしようもないのですが, malloc/free は最大限控えるように考えてほしいと思います. 僕もかなり malloc/free には苦しめられた記憶がありますので...
メモリの解放タイミングを幾分か考える必要がないのでかなり気持ちが楽になると思います. 精神の安定は良きことです.

Case 2 では for 文を可能な限り書くなというのをメッセージとして書いたのですが, 単純な for-each や for ループに置き換えづらいものに対しては使いづらいかも知れません. それでも, 単純なループならほとんど に用意されている関数で置き換えられますので, 積極的に使うことをお勧めします. その点では Boost.Range がかなりお勧めです. (Boost.Range アルゴリズム関数のすすめ - boostjp)

Case 3 ではリソースの管理を考えました. 単純に free/delete するものではないリソース (fclose や glDeleteBuffers など) でも, カスタムデリータ機能を使って安全に解放できます. unique_ptr や shared_ptr を用いてリソースをもっと手軽に扱っていきましょう.

最後に Boost を若干紹介しました. 僕は今 MPI を用いてなんとも言えない (言っていいのかよくわからない) 計算をしているのですが, Boost.MPI は非プリミティブな型であっても Boost.Serialization を用いてシリアライズし, 転送できるようになっています. そのため std::vector などを転送することが簡単で *5 また自分で作った struct なども所定の手順を踏めば転送できるようになっています.

Cer の皆さんにとって良きメッセージになったかは分からないですが, .c を .cpp に変え, gcc を g++ に変えてみることから始めてみませんか?

*1:恐らく一般的には

*2:バグを減らすには, コードを書かないことが望ましい

*3:ただし, std::shared_ptr で配列を渡す場合は若干手順が増える. [http://d.hatena.ne.jp/faith_and_brave/20110920/1316507398:title]

*4:C++ なら本当は を使うべきかもしれないが出力フォーマット指定が...

*5:Boost.MPI に用意されている