夏休みなのでbjam始めてみます 3日目

0xの使い方やライブラリ回りは、プロのbjammerことid:Flast先生に教えてもらいました。

複数のソースから1つの実行ファイルを作りたい

単に依存関係を並べればいいようです。

Jamroot:

exe a : a.cpp b.cpp ;
$ $BOOST_ROOT/bjam release
...found 13 targets...
...updating 6 targets...
common.mkdir bin
common.mkdir bin/darwin-4.6
common.mkdir bin/darwin-4.6/release
darwin.compile.c++ bin/darwin-4.6/release/a.o
darwin.compile.c++ bin/darwin-4.6/release/b.o
darwin.link bin/darwin-4.6/release/a
...updated 6 targets...

システムに入ってるライブラリを使いたい

requirementsの所にで名前を教えてあげればいいようです。ただし、先頭のlibはいりません(libz→z、libpng→png)。システム標準でない場所にライブラリが入っている場合はで場所を教えてあげます。

Jamroot:

lib z : : <name>z <search>/opt/local/lib : : <include>/opt/local/include ;
exe a : a.cpp z ;

a.cpp

#include <iostream>
#include <zlib.h>

int main() { std::cout << zlibVersion() << '\n'; return 0; }
$ $BOOST_ROOT/bjam release
...found 11 targets...
...updating 5 targets...
common.mkdir bin
common.mkdir bin/darwin-4.6
common.mkdir bin/darwin-4.6/release
darwin.compile.c++ bin/darwin-4.6/release/a.o
darwin.link bin/darwin-4.6/release/a
...updated 5 targets...
$ ./bin/darwin-4.6/release/a
1.2.5

C++0xを使いたい

projectのrequirementsでオプションを追加してあげればいいようです。

Jamroot:

project : requirements <cxxflags>-std=c++0x ;
exe a : a.cpp ;

夏休みなのでbjam始めてみます 2日目

1日目と同日公開ですが、2日目です。はい。デフォルトのvariantがdebugなので、ぼーっとしてると遅いバイナリを作ってしまいそうで怖いですね。3日目に続くかは謎です。

variantオプション

bjamはデフォルトでdebugモードでビルドされます。ビルドモードを変更するにはbjamの引数にvariantオプションを渡すか、単にdebugやreleaseと引数に加えます。なお、コンパイルオプションその他を一杯表示させるには、 -d+2 オプションを渡します。逆に、出力を最小限にしたい場合は-d0を渡します。

Debug:

$ $BOOST_ROOT/bjam -d+2 debug
[]
"g++-mp-4.6"  -ftemplate-depth-128 -O0 -fno-inline -Wall -g -dynamic -gdwarf-2 -fexceptions -fPIC     -c -o "bin/darwin-4.6/debug/a.o" "a.cpp"
[]

Release:

$ $BOOST_ROOT/bjam -d+2 variant=release
[]
"g++-mp-4.6"  -ftemplate-depth-128 -O3 -finline-functions -Wno-inline -Wall -dynamic -gdwarf-2 -fexceptions -fPIC  -DNDEBUG   -c -o "bin/darwin-4.6/release/a.o" "a.cpp"
[]

なお、variantは複数指定できるので、releaseとdebugを同時にビルドできます。

Debug+Release:

$ $BOOST_ROOT/bjam -d+2 debug release
[]
"g++-mp-4.6"  -ftemplate-depth-128 -O0 -fno-inline -Wall -g -dynamic -gdwarf-2 -fexceptions -fPIC     -c -o "bin/darwin-4.6/debug/a.o" "a.cpp"
[]
"g++-mp-4.6"  -ftemplate-depth-128 -O3 -finline-functions -Wno-inline -Wall -dynamic -gdwarf-2 -fexceptions -fPIC  -DNDEBUG   -c -o "bin/darwin-4.6/release/a.o" "a.cpp"
[]

-j オプション

GNU Makeと同様に-jオプションを指定すると、平行ビルドで実行されます。多数のコアがあるマシンで実行する場合は、ビルド時間が短縮される可能性があります。
boost本体レベルの規模となると物理8コアのマシンで、ビルド時間が-jなしで604秒、-j8付きで93.8秒となり、8倍とはいきませんが、6倍程度高速化されています。

夏休みなのでbjam始めてみます 1日目

bjamが欲しいので、まずはboostをダウンロードして展開し、bjamをビルドします。そして、boostを展開したディレクトリをBOOST_ROOT環境変数に定義しておきます。BOOT_ROOTはbjamの動作上も必要です。

$ export BOOST_ROOT=/path/to/boost
$ cd $BOOST_ROOT
$ ./bootstrap

boost 1.47.0からbootstrapで作成されるbjamの実行ファイル名が、"bjam"と"b2"の2つになっています。1.47の段階ではこれらのファイルの内容は同一で単なるコピーのようですが、以降はbjamで統一します。

Mac環境で、Appleによって提供されていないgccを使う場合は、以下の修正を行う必要があります。(例えばMacports) -no-cpp-precompというオプションはApplegccにしか存在しないためです。(本当にこの修正で大丈夫なのか、よくわかんないですけど)

--- $BOOST_ROOT/tools/build/v2/tools/darwin.jam.org	2011-07-30 19:12:42.000000000 +0900
+++ $BOOST_ROOT/tools/build/v2/tools/darwin.jam	2011-07-30 19:12:49.000000000 +0900
@@ -498,7 +498,7 @@
 flags darwin.compile OPTIONS <link>shared : -dynamic ;
 
 # Misc options.
-flags darwin.compile OPTIONS : -no-cpp-precomp -gdwarf-2 -fexceptions ;
+flags darwin.compile OPTIONS : -gdwarf-2 -fexceptions ;
 #~ flags darwin.link OPTIONS : -fexceptions ;
 
 # Add the framework names to use.

さて、a.cppとJamrootファイルを用意して適当にコンパイルしてみます。

a.cpp:

#include <iostream>
int main() { std::cout << "Hello bjam\n"; return 0; }

Jamroot:

exe a : a.cpp ;
$ ls
Jamroot a.cpp
$ $BOOST_ROOT/bjam
...found 9 targets...
...updating 4 targets...
common.mkdir bin/darwin-4.6
common.mkdir bin/darwin-4.6/debug
darwin.compile.c++ bin/darwin-4.6/debug/a.o
darwin.link bin/darwin-4.6/debug/a
...updated 4 targets...
$ ./bin/darwin-4.6/debug/a
Hello bjam

あ、2日目に続くかは謎です。

ZeroMQとfork(2)ではまった

僕はかなりはまったのですが、ZeroMQのcontextはfork(2)で複製できません。以下のようなコードを書くと死にます。forkする前にcontextを破棄するか、preforkしておく必要があります。

URLを忘れたのですが、MLでもpreforkしておいてよみたいな流れだったので、バグとかではなく、こういうものなのだと思います。

#include <iostream>
#include <stdexcept>
#include <zmq.hpp>
#include <unistd.h>

bool xfork() {
    pid_t const pid = fork();
    if (pid == -1) {
        throw std::runtime_error("fork");
    }
    return pid == 0;
}

int main() {
    zmq::context_t ctx(1);

    if (xfork()) {
         // なんかする
    } else {
        // なんかする
    }
    return 0;
}

Boost.Phoenixのうーん?

phoenix::letの中で、引数(_1, _2, arg0, arg1系)は参照できず、コンパイルエラーになります。*1この挙動はとても不便ですが、仕様としてこうなのか、実装がバグっていてこうなっているのかはわかりません。

void f() {
    namespace phx = boost::phoenix;
    using boost::phoenix::arg_names::_1;
    using boost::phoenix::local_names::_a;

    int x = 111;

    auto const f = phx::let(_a = 222) [ std::cout << _a << _1 ];
    f(x);
}

ところが、letで引数をローカル変数に束縛すると、let内部でも引数が参照できるようになります。

void f() {
    namespace phx = boost::phoenix;
    using boost::phoenix::arg_names::_1;
    using boost::phoenix::local_names::_a;

    int x = 111;

    auto const f = phx::let(_a = _1) [ std::cout << _a << _1 ];
    f(x);
}

うーん…?

*1:scoped_environmentのEnvがbasic_environment<>になって、引数がどっかにいってしまう。

Boost.Serializationの出力形式としてMessagePackを使えるようにしてみた

Boost.ArchiveのMessagePackバックエンドに用いた実装です。まだ試作段階ですが、Boost付属のSerialization用テストは一通り合格しているので、とりあえず動くと思います。

ソースコードgithubにあります。なお、ビルドツールはwafを使用しているため、ビルドにはpythonが必要です。もっとも、cppファイル2個しかありませんが。

メリット

Boostには標準でバイナリアーカイブが付属していますが、バイナリアーカイブクロスプラットフォームではありません。したがって、ネットワーク通信など、送り手と受け手の環境を一致させることが困難な状況でバイナリアーカイブは適切な選択肢ではありません。また、テキストやXMLアーカイブを使用すれば可搬性は高まりますが、データサイズが肥大化してしまいます。

MessagePackはプラットフォームに依存しないフォーマットを採用しているため、バイナリアーカイブと比較して、より広範囲にわたるデータ交換を可能にします。MessagePackは標準で一部の型(例えばstd::vector)の変換アルゴリズムを持っていますが、Boost.Serializationの枠組みに対応することにより、より多くの型をシリアライズすることができます。また、MessagePackは様々な言語でバインディングが提供されているため、他の言語とのデータ交換も可能でしょう。(ただし、Boost.Serializationの変換ルールを他の言語で実装しなければならないため、それなりの手間がかかると思いますが)

未実装

以下の機能は未実装です。

  • woarchive, wiarchive
  • wchar_t型の扱い
  • polymorphic archive

oarchive, iarchiveではwchar_t型はマルチバイト文字列に変換してからシリアライズされます。同様にwoarchive, wiarchiveではマルチバイト文字列はwchar_t型に変換してからシリアライズされますが、現時点では文字変換は未対応です。wchar_t型は数値型として、std::wstringはc_str()で得られる文字列をそのままRAW型として書き出しています。

フォーマット

基本的には、データをそのまま書き出します。

struct A {
    bool a; int b;
    template <class Archive>
    void serialize(Archive& ar) {
        ar & a & b;
    }
};
struct B {
    A a; float b;
    template <class Archive>
    void serialize(Archive& ar) {
        ar & a & b;
    }
};

というクラスがあった場合、

    msgpack::packer<Stream> p;
    B b;
    p << b.a.a << b.a.b << b.b;

とほぼ等価です。実際には、ファイル先頭にBoost.Serializationのヘッダが付与されるため、若干肥大化します。また、Name Value Pair (NVP)の名前は無視され、値のみが出力されます。

clang のライブラリの方で遊んでみた

clang のコンパイラではなく、ライブラリの方で遊んでみた。使用した clang(+LLVM) のバージョンは Subversion レポジトリより取得した r127064 。ちょっと古いが気にしない。

clangのライブラリの方のドキュメントが見つけられなかったので、主にclang tutorialを参考にした。しかしながら、clang tutorialが書かれたのは2008年であり、現在の clang ではコンパイルエラーになるコードもある。

今回作成したのは、main関数が引数を取るか取らないかを出力するプログラムである (clang::FunctionDecl::isMain関数を使えば、ある関数がmain関数であるかどうか判定できるので楽できる)。clang::CompilerInstanceの使い方がこれでいいのか、かなり自信がない。以下ソースコード

コンパイルは以下のようにする*1。 ただし、$(LLVMDIR)はLLVMがインストールされているディレクトリを表す。

$ clang -std=c++0x -fno-rtti -Wall -I$(LLVMDIR)/include \
  -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -o maintype maintype.cpp $(LLVMDIR)/lib/lib*.a

*1:どのライブラリをリンクすればいいのか、調べるのがめんどかったので、全部リンクすればいいよねっていう(ry