ラベル OSS の投稿を表示しています。 すべての投稿を表示
ラベル OSS の投稿を表示しています。 すべての投稿を表示

2014年6月1日日曜日

MPFRについて:任意精度数値計算ライブラリ

以前、1億桁のπの計算のためGMPを使いました。しかし、これには四則演算しか存在していなくて、通常の科学計算にはとても使えませんでした。
その後、調べていたらGMPを元に三角関数等をできるようにした、MPFR、MPCがありました。これら3つのライブラリは何とgccをビルドするのに必要なパッケージだとかで(何に使ってるんだ?)、最新のgccを使いたければインストールしておかないといけません。依存関係があり、GMP←MPFR←MPCという感じになるので、その順番にインストールしていかないといけません。大雑把に概要をいうと、以下のようになります。

・GMP
 任意精度算術演算ライブラリ

・MPFR
 こちらも任意精度の浮動小数点数演算ライブラリ(各種関数あり)

・MPC
 複素数演算ライブラリ

MPFRの簡単なサンプルを以下に示します。(これらのライブラリはCなのに注意)


/*                                                                                                     
mpfrの使い方を調べてみる                                                                        
 */

#include <stdio .h>
#include <stdlib .h>
#include <gmp .h>
#include <mpfr .h>

int main()
{
  mpfr_t work;

  mpfr_init2(work, 200);  // 有効桁数200bitで初期化                                                  
  mpfr_set_d(work, 1.0, MPFR_RNDD);  // double変数を代入                                              

  printf("work is ");
  mpfr_out_str(stdout, 10, 10, work, MPFR_RNDD);  // 出力 2個目はn進数での出力指定                   
  // 3個目は何桁出力するか(size_t) '0'を指定すると当該変数の有効桁数すべて出力する                     
  putchar('\n');

  mpfr_const_pi(work, MPFR_RNDD);
  printf("pi is ");
  mpfr_out_str(stdout, 10, 0, work, MPFR_RNDD);
  putchar('\n');

  mpfr_clear(work);  // メモリの解放                                                                   

  return 0;
}

2回目の処理でπをセットしています。当然、任意の桁数を指定できますが、おそらく以前やったようなアルゴリズムで毎度必要な桁数を計算しているんでしょう。
ということは、桁数が大きいとそれに応じて処理時間がかかることになる?ちょっと計測してみました。(マシンはMacBookPro Retinaです。現在の我が家ではこいつが最速マシン)

①πの場合
以下の関数の実行前後でclockを取得し、関数の処理時間を調べてみました。

  gettimeofday( &tb, NULL );
  mpfr_const_pi(work, MPFR_RNDD);  // こいつの時間計測を行う
  gettimeofday( &ta, NULL );

変数workの精度を変えて計測してみました。(それぞれ数回計測し、5msec単位で丸めてます)

100,000bit 15msec
500,000bit 140msec
1,000,000bit 340msec
2,000,000bit 800msec

案の定、桁数と処理時間の関係はリニアではありませんね。

②三角関数の場合(atan)
三角関数(時間のかかりそうなatan)で計測してみました。

  gettimeofday( &tb, NULL );
  mpfr_atan(rslt, work, MPFR_RNDD);  // こいつの時間計測を行う
  gettimeofday( &ta, NULL );

100,000bit 15msec
500,000bit 140msec
1,000,000bit 340msec
2,000,000bit 795msec

う〜ん、ほとんどπと変わりませんね。たまたま演算量が同じ程度だったのかもしれません。
ただ何れにしても通常の倍精度浮動小数点の計算なんかとは比べ物にならないくらいの処理時間がかかっています。こういうライブラリがあるから安易に「100桁の精度で計算してよ」(1桁、約4bit弱必要なので400bitくらいですか)と依頼されても事前に注意しとかないと大変なことになります。
あとプログラム中では"mpfr_t"と変数を定義していますが、この実体はポインタで、必要な桁数のエリアをmallocしてそのポインタを保持しているだけです。従って、この変数自体はthread safeではありません。マルチスレッドのプログラムでこれを使おうって時には注意が必要です。

2014年5月2日金曜日

Coin 3D:現状とビルド

Coin 3Dってご存知でしょうか?3DのGraphics Libraryですが、20年位前その分野で有名だったのがSGIというコンピュータ会社でした。そこが今では業界標準になっているOpenGL(大胆にも、GLはGraphics Libraryの略です)を作り、更により上位のライブラリとしてOpenInventorを作りました。(Inventorは1995年くらいだったかな?)
当時のSGIは非常にバカ高いWS(Work Station)を販売しており、そこでしかOpenInventorは使えませんでした。そのとき、有志で互換のライブラリを作ろうと出てきたのがCoinでした。(皆、興味がなくなったのかぐぐっても、情報がないですね。)

1.概要
2009年頃書いていたブログ(今とは別の所で書いてました)にその当時のCoinの状況を書いてました。
SGIの経営が2000年を越えてしばらくしてから怪しくなり、Inventorの権利を別会社に販売したり、OpenInventorと命名して(Openを頭文字に入れた)、ソースを公開していました。(2005年くらいだったかな。OpenInventor2.1.5が最後に公開されたソースコードで、当時RedHat9でビルドができました。)
結局SGIは3Dグラフィクス分野からは手を引き(日本SGIに確かその分野を譲渡し、NECが資本を注入してました。)、完全にOpenInventorのソースコードが中に浮いてしまった状態になりました。(その前にInventorのライセンスを買った会社は今でもOpenInventorのメンテナンス、といっても新しいプラットフォームへの対応、バグとり程度しかしてませんが、これがまた高い!こちらのソースコードは非公開なので、全く独立にメンテされています)
2009年の2、3年前にCoinがフリーになったOpenInventorのソースコードを使うことの許可をSGIに得て(それまではAPI定義だけを元に、独自の実装をしていた)、メンテナンスを行うという発表を見た記憶がありました。その後、2009年に調べてみたら、なんかCoin 3Dがどこかの会社(資源探査会社?)に買い取られ、Qtのようなダブルライセンスになっていました。当時、CentOS5でビルドしてみましたが、あっさりビルドできて動いた記憶があります。(ただ見た目がまだかなり、本物のOpenInventorとは違っているところがありました。はっきりいって「いけてない」感じのデザインでした。)

さて2014年現在ですが、なんとCoin3Dはその会社が放棄していました!!(2012年)商売にならないと判断されたのでしょう、現在では一部の有志だけで公開されているようです。(JIRAで有名なAtlassianが行っているSourceForgeのようなサービスでプロジェクトを公開していました。リポジトリはgitではなく、Mercurialを使っていました。そこではwikiサービスも行っており、JIRAのようなタスク管理も動いていましたが、活動があまり活発ではありませんでした。)
従って、ソースコードも2012年から更新されていないようです。

2.Coin 3Dのビルド
さて現状のバージョンはCoin-3.1.3となっています。これをDLしてきて、以下の環境でビルドしてみます。

・Ubuntu 13.10
・g++-4.8.1

(1)configureでのエラー
何も考えずに、no-optionでconfigureすると以下のエラーがでて、configureができなかった。cc_debugerror_post()の関数定義で、先行定義してあるデバッグ用関数において、gcc-4.7以降ではtemplateの解釈が変わったらしく、namespaceの範囲解釈が変わり問題がでるようになった。
 元々、trickyな定義がしてあったらしい。解決方法が見つからないので、debugをスルーするようにした。

$ configure --disable-debug

これで当該エラーはでなくなり、ビルドができるようになった。

(2)g++のバグ
ビルドをしてみると、以下のエラーがでた。

In file included from ../../../Coin-3.1.3/src/base/dict.h:31:0,
from ../../../Coin-3.1.3/src/fonts/freetype.cpp:142:
/usr/include/c++/4.8/cstdlib: In function ‘long long int std::abs(long long int)’:
/usr/include/c++/4.8/cstdlib:174:20: error: declaration of C function ‘long long int std::abs(long long int)’ conflicts with
abs(long long __x) { return __builtin_llabs (__x); }
^
/usr/include/c++/4.8/cstdlib:166:3: error: previous declaration ‘long int std::abs(long int)’ here
abs(long __i) { return __builtin_labs(__i); }


どうもabs()関数の多重定義に関するエラーらしい。ただ問題が、abs()関数を使うところではなく、abs()関数をinline定義しているcstdlibをincludeしているところで発生しているため、どう対処したらいいのかがわからない。

英語でぐぐってみたら、gcc-4.8のバグで、cstdlib(c++用)がstdlib.h(c用)を含めてマクロ定義を多重に読み込むのを防ぐときのバグらしい。(cで書かれたプログラムをc++で流用するときが多分にあり、そのとき両方がincludeされることが多々発生するが、同じマクロ定義が行われていることが多い。その問題を解決するのにg++側でバグがでているっぽい)
別のプロジェクトのブログに解決法として、両方includeしているようならcstdlibをやめろ(stdlib.hだけにしろ)というのがあったので、ソースを一部修正してみると、ビルドが先に進んだ。
(src/base/dict.h:31  cstdlibのincludeをコメントアウトした)

(3)namespace指定の問題
更にビルドが進むと、以下のエラーがでた。

g++ -DHAVE_CONFIG_H -I../../include -I../../../Coin-3.1.3/include -I../../src -I../../../Coin-3.1.3/src -D_REENTRANT -DNDEBUG -DCOIN_DEBUG=0 -DCOIN_INTERNAL -g -O2 -W -Wall -Wno-unused -Wno-multichar -Woverloaded-virtual -MT glyph.lo -MD -MP -MF .deps/glyph.Tpo -c ../../../Coin-3.1.3/src/fonts/glyph.cpp -fPIC -DPIC -o .libs/glyph.o
../../../Coin-3.1.3/src/fonts/glyph.cpp:41:12: error: ‘std::free’ has not been declared
using std::free;


std::freeというnamespaceがg++-4.8にはないらしい。とりあえずコメントアウトしてみる。

 free()はstdlib.h内に定義してあるが、includeしてないせずにnamespaceの定義だけでエラーを逃げようとしたらしい。他にもmallocでも別の近い箇所でエラーでる。
どうも(2)で述べた、stdlib.hとcstdlibの多重includeで問題がおきて、一部ソースではstdlib.hをincludeするのをやめ、using std::freeとかのnamespace定義で逃げようとしたらしいが、それが問題になったらしい。(先に、cstdlibのincludeをコメントアウトしたせいか?)

 かわりに#include <stdlib.h>してやれば先に進んだ。
結果として、これでビルドはパスし、make installができた。(/usr/local以下にヘッダ、ライブラリはインストールされた)


3.サンプルプログラムの作成
さて実際にビルドが成功したかを確認するために、サンプルプログラムとして、Inventor mentorの最初にある、Red Coneをコンパイルしてみます。
しかし、これだけではビルドができません。よく見たら、Coin-3.1.3のtarボールにあるのはOpenInventor本体だけであり、それをwindow上に表示するためのSoXt(WindowsならSoWin等)がありません。Coin3DのHPに別途、SoXt-1.3.0があり、それをインストールする必要があります。

(1)configureでのエラー
何も考えずにconfigureすると、Coinと同じようにcc_debugerror_post()の関数定義でエラーがでました。同じように、--dsiable-debugオプションで逃げます。

(2)実行時にエラーがでる
これでSoXtのビルドは成功、インストールできました。しかしサンプルプログラムを実行すると以下のエラーがでます。

Coin error in SoXtGLWidget::buildWidget(): SoXt does not support detecting best visual/colormap without the Xmu library (yet)

よくわかりませんが、ビルド環境を調べてみるとXmuのランタイムは入っていますが、開発環境(libxmu-dev)までは入ってませんでした。試しにインストールしてSoXtを再ビルドしてみましたが、状況はかわりません。

このエラーを出している箇所を調べてみると、場所は、SoXtGLWidget.cpp:1064らしい。

#ifndef HAVE_LIBXMU
SoDebugError::post("SoXtGLWidget::buildWidget",
"SoXt does not support detecting best visual/colormap without the Xmu library (yet)");
exit(1);
#else // HAVE_LIBXMU


内容的には、Xmuライブラリを持っていなかったらとのフラグでここに入っているが、当該環境にはXmuライブラリはインストール済みである。configureに問題があるらしい。
config.logを見ると、環境調査の中で以下の箇所で問題がでていた。

configure:22291: g++ -o conftest -g -O2 -W -Wall -Wno-unused -Wno-multichar -Woverloaded-virtual\
  -DNDEBUG -DSOXT_DEBUG=0 -DSOXT_INTERNAL      conftest.cpp -lXmu -lXext  -lSM -lICE -lX11  -ldl\
  >&5
conftest.cpp:31:41: fatal error: X11/Xmu/Xmu.h: No such file or directory
                 #include <X11/Xmu/Xmu.h>
                                         ^
compilation terminated.

おそらくこのエラーのせいで、Xmuライブラリが環境に存在しないと判断されたらしい。しかしなぜこんなエラーが出てくるのか不明。(当環境には、当該位置にXmuは入っている)
再度、通常のターミナルでSoXtをmakeし直しました。(前回はおそらくemacs内のshellで行っている。通常、ログをあとで確認するためよくemacs内のshellでビルドをしているくせがついている。)
するとXmu関係のエラーがconfig.log内にでなくなり、SoXtを再インストールしたら動きました!


4.サンプルプログラムの実行
2個ほど、実行結果を示します。

(1) HelloCone
まずはmentorの最初にでてくる一番シンプルなものです。

画面の真ん中に、赤い3角錐が表示されるだけです。

(2)ExaminarViewerの実装
最初のCoinにはまだ実装がなかったんですが、OpenInventorではとても便利なViewerだったExaminar Viewerです。

ぱっと見、大して変わらないように見えますが、このViewerではマウスで表示中のモデルを自由に視点を回したり、拡大/縮小ができます。右端の一文字のボタンが何か昔のSGIの実装とは違いますが(確かアイコンで機能が表示されていたはずなんですが、一文字になってますね)、とりあえず動作には問題がありません。

と、まあ一応は動くようです。(g++周りの実装が変わったことによるエラーには少し悩みました。昨年末、やっとc++第4版の仕様が確定したらしいという話を聞いたせいか、多少実装に変更があったようです。)

Ubuntuも14.04LTSが発表されたところで、今の環境だと「アップデートするか?」と毎回聞かれますが、こんな時にg++のバージョンが変わってしまったら訳がわからなくなりますから、そのままにしてあります。

2014年3月30日日曜日

vagrant覚書

あちこちで仮想環境構築ツールのvagrantについて書かれてますが、操作コマンドと実際のデータファイルの関係を調べたので記録しておく。(応用きかせようとするとき、実際の仮想マシンのディスク等がどうなっているか理解しておいた方がいいので)

仮想マシンは昔からつきあっていますが(この世界では有名なVMwareが1.5の時から使っていました。当時英語のHPを一生懸命読んで、DL販売で購入しました。)、色々な実験をするときいちいちPCを準備しなくてもいいので重宝してました。しかし仮想マシンの構築はそれなりの手間がかかり、慣れてない環境を構築しようとして失敗したときなどは、再度仮想マシンの構築からやった方が早い時が多く、大変でした。(仮想マシンのディスクイメージのsnapshotや、コピーを保存しておいたりして、再構築しなくてもいいようにしましたがGB単位のデータ量になるのでコピーだけでも結構時間がかかります。)その点vagrantだとテンプレートがあればコマンド一発で仮想マシンのセットアップをしてくれるので、心置きなく環境構築を試すことができます。(といっても基本GUIなしの環境ですが)一通り手順と、よく使うコマンドの簡単な説明一覧を示しておきます。

1.Virtual Boxをインストールする
(個人的にはVirtual Boxにはあまり良いイメージは持ってません。発表当時はVMwareに比べて実行速度がかなり遅く、すぐに使わなくなってしまいました。今回改めて使ってみましたが、随分早くなったな〜という感じです。vagrantが基本的にVirtual Boxのテンプレートが多いので、こちらでいきます。)

2.vagrantをインストールする

3.テンプレートの準備
vagrant box add (テンプレート名) (URL) ;仮想マシンのテンプレートを取得する(今後、引数のテンプレート名で操作する)
list ;システムにインストールしてあるテンプレートの一覧
remove (テンプレート名) ;テンプレートの削除

4.仮想マシンの構築(仮想マシンの設定ファイル:Vagrantfileの場所で下記のコマンドを実行)
vagrant init (テンプレート名) ;vagrantの初期化(Vagrantfileの作成)
up ;仮想マシンの起動
ssh ;仮想マシンと接続
status ;仮想マシンの状態
suspend ;実行中の仮想マシンの一旦停止
resume ;一旦停止した仮想マシンの実行
status ;仮想マシンの状態
reload ;仮想マシンの再起動
halt ;仮想マシンの停止
destroy ;仮想マシンの削除(Virtual Box上の仮想マシンが削除される)

5.テンプレート・データの保存場所
~/.vagrand.d/boxes/(テンプレート名)
基本的にはvirtual boxの仮想ディスクイメージが入っている

6.vagrant設定ファイル
~/(任意)/Vagrantfile
仮想マシンの起動等を行う場所、任意のパス名を自分で設定する。
initコマンドにより作成される。

7.Virtual Boxの仮想マシン
~/VirtualBox VMs/(仮想マシン名)
初回のupコマンドで作成される。
vagrant設定ファイルのパス名を頭文字として、仮想マシン名は作成される。
vagrant設定ファイル毎に仮想マシンの実体は作成されるので、一つのテンプレートから複数の仮想マシンを作成・実行できる。

今回、UbuntuとMacの両方でvagrantを試してみましたが、全く同じような動作をしてくれました。(Rubyベースで作成されているとのことですが、それでも異なるOSで同じように動いてくれるというのはなかなか無いことです。)
仮想マシンの作成/起動は、5.、6.、7.の順番に行われますが、それぞれのコマンドで作成されるデータ、およびその関係を下図に示します。

説明を聞いて、最初の1個の仮想マシンを起動するまではなんとなくわかった気になるんですが、いざ複数の仮想マシンを起動したい、同じテンプレートで複数の仮想マシンを動かしたい、と考えたときどうなるんだろうと思い、データがどこに保存されているかまとめてみました。
従って、調子にのって多数の仮想マシン、テンプレートを作ると、~/.vagrant.d/以下、~/VirtualBox VMs/以下のHDD容量が大変なことになりますので注意しないといけません。

後、使っていて気づいたんですが、CentOSのテンプレートから作成した仮想マシンにおいて外部へのポートが閉じた設定(今のクライアント指向のデストロはこれが基本です)のままテンプレートが作成されていたものがありました。そのため$ vagrant sshで仮想マシンに接続はできますが、ping等まったく通らず、サーバーの実験をしようと思う時、ちょっと面倒くさいです。(逆に、vagrant sshはどうやって接続しているんだろう?と思ってしまいます。)

PS.
上記、仮想マシンの外部との接続ですが、その後CentOSでも設定でpublic_networkを一度有効にして新しいethのルート(仮想マシンに対するネットワークデバイス?)を設定してくるか聞いてきた時、有効にしたら仮想マシン上で新しいeth1が認識されるようになり、CentOSでも外部からのpingに応答するようになりました。(これは複数のEther HWがMacにあったためのようです。Ubuntuでは聞かれませんでした。)
その後はpublic_networkを無効にしても外部(host PC)からのpingは通るようになったままになりました。Virtual Boxのネットワークデバイス周りの設定を変更してしまったようです。

2014年2月13日木曜日

GMPの紹介

何となくπを多数桁(100万桁)のオーダーで計算するのに、今のPCだとどれくらいかかるんだろうと思い、ネットをググっていたら、アルゴリズムやプログラムの紹介をしているサイトがありました。
そこで使われているライブラリがGMP(Gnu Multipule Precision Arithmetic Library)でして、以前少し耳にはしていましたが詳しいことを調べてなかったので、ちょっと見てみました。
(注:πの多数桁は計算プログラムがサイトに乗っており、その方の計算時間、自分で試した計算時間を見ても1億桁やっても10分ちょっとでできてしまうことがわかり、興味がなくなりました。ただメモリの消費量を見ていると、32bit OSではギリギリに近くなってます。これ以上の桁数の計算をしようと思うと、64bit OSにしないと。)

さてGMPですが、任意の精度で数学計算を行うライブラリです。(もちろんメモリが許す限り、という大前提はつきますが。) https://gmplib.org/ に本家があります。最新が2013.9更新になっていますから、まだ活動は続いているようです。
とりあえずインストールの説明をします。

1.ソースをDLしてくる。
私は、gmp-5.1.3.tar.bz2を落としてきました。リリースノートを見てみると、CPU(ここではintel, ARMが対象になっていました)毎に最適化をしてより高速化を目標にしているようです。

2.tarボールの解凍、ビルド、インストール
適当な作業用ディレクトリを作り、そこで先ほどDLしてきたtarボールを解凍します。今回、自分はPCとして、Ubuntu 13.10、Mac OSX Mavericksの2台で試してみました。(Mac OSXの方はインストールに少し難があると本家サイトの中に記載がありましたが、特に問題はおきませんでした。ただ困ったのは、OSXでは/usr/local以下にインストールしたものをデフォルトではコンパイル対象にしてくれなくてサンプルプログラムのコンパイルに少し手間がかかりました。)

$ ./configure --enable-cxx  (c++でも使えるようにオプション追加です)

$ make

$ make check        (単体試験を多数やってくれます)

$ sudo make install     (/usr/local以下にライブラリをインストールします)

3.テストプログラム
雰囲気が味わえる程度の簡単なものを示します。

(1)整数の加算
まず基本として整数の加算です。


#include 
#include 

int main (int argc, char* argv[]) {

  mpz_t a, b, c;
  mpz_init(a);
  mpz_init(b);
  mpz_init(c);

  mpz_set_ui(a, 12345);
  mpz_set_str(b, "12345678910987654321", 10);

  mpz_add(c, a, b);
  mpz_out_str(stdout, 10, a);
  printf("\n");

  mpz_out_str(stdout, 10, b);
  printf("\n");

  mpz_out_str(stdout, 10, c);
  printf("\n");

  mpz_clear(a);
  mpz_clear(b);
  mpz_clear(c);

  return 0;
}



最初に整数変数a,b,cを定義しています。(特にprecision(精度)は規定していませんが、整数の場合は必要なだけ確保されるようです)
面白いのが、"mpz_set_str()"関数ですが、通常のC言語では扱えないような多数桁の整数を初期値に使えるように、文字列で設定できる関数です。c=a+bを行い、最後にa,b,cを出力しています。

・実行結果
12345
12345678910987654321
12345678910987666666


ちゃんと加算されていることがわかります。"mpz_out_str()"が出力関数なんですが、第1引数がFILE*で今は標準出力にしていますが、もちろんファイルへの出力にも使えます。また第2引数の"10"ですが、これは基数をいくつで出力するかです。(といっても、8,10,16くらいしか使わないと思いますが)

(2)階乗の計算
多数桁の計算が自由にできるということで、サンプルで階乗の計算プログラムを示します。


#include 
#include 

int main (int argc, char* argv[]) {

  unsigned long int i;
  mpz_t a;

  mpz_init(a);
  mpz_set_ui(a, 1);

  for (i = 1; i <= 100; i++) {
    mpz_mul_ui(a, a, i);
  }

  printf("%ld! = ", (i-1));
  mpz_out_str(stdout, 10, a);
  printf("\n");

  mpz_clear(a);
  return 0;
}


・実行結果
100! = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

今回は結果の表示を考えて100までにしましたが、10000でも簡単に計算してくれます。

(3)浮動小数点の計算
基本は整数と変わりません。ただGMPの内部では浮動小数点は指数部は通常の浮動小数点形式と同じ(つまり固定長)、その代わり仮数部が任意の桁数にできます。また、変数を宣言/初期化する時に、その仮数部の桁数(正確にはbit長)を指定してやらないといけません。

・例
int digits1 = 20;
int prec1 = digits1 * log2(10);

mpf_t a;

mpf_set_default_prec(prec1);
mpf_init(a);


例に示したものでは変数aの仮数部を20桁で持つように設定してます。(mpf_set_default_prec()で設定するときbit長で指定しないといけないので、20桁が何ビットになるか計算してます)

但し、いくら仮数部を多数桁もてたとしても、指数部の極端に異なる浮動小数点同士で演算を行えば桁落ちは発生します。そのあたりの注意事項は変わりません。(仮数部をかなり大きく持てるので、計算誤差は大分減らせるでしょうが)

(4)分数の計算
GMPではなんと分数の計算もできます。


#include 
#include 

int main (int argc, char* argv[]) {
 mpq_t a, b, c;

 mpq_init(a);
 mpq_init(b);
 mpq_init(c);

 mpq_set_si(a, 4, 7);
 mpq_set_si(b, 3, 8);

 mpq_add(c, a, b);

 mpq_out_str(stdout, 10, a);
 printf("\n");

 mpq_out_str(stdout, 10, b);
 printf("\n");

 mpq_out_str(stdout, 10, c);
 printf("\n");

 return 0;
}



内部的には整数の分子と分母で持っているのかな。上記は、4/7 + 3/8の計算をしています。

・実行結果
4/7
3/8
53/56


こんな感じで、ちゃんと分数形式で出力もしてくれます。当然、分数で計算後、浮動小数点への変換をしてくれる関数も持っていますので、やたら沢山の(整数の)分数を掛けたり、割ったりしなければいけない計算式の計算がかなり誤差を気にしなくてもよくなります。

さてこんな便利なGMPライブラリなんですが、残念ながら完全にはWindowsに対応していません。独自にVisual Studioのprojを作っている人がいましたが、make check(単体試験)まで対応できているんだろうか?Windowsにインストールしている人はほとんどcygwinでした。(Linux, Mac使っている人間が今更cygwinを使う必要性が殆ど無いので、これは試していません。)
configureをCMakeあたりにしてくれると面白いんですが、それでも単体試験の問題は残るでしょうし。(本家のサイト読んでたら、とにかくmake checkは必ずやれとうるさく書いてありました。OSの環境、CPUのバージョンの微妙な違いでFPUの結果が変わったりしますから、そこでライブラリの保証をしないと、危なくて使えないモノになってしまいますしね。)

2012年4月6日金曜日

enghelperをインストール

ここのところruby環境をubuntu11.10で整えるのに頑張っていましたが、それもenghelperを動かしてみたかったからです。これは英次郎のデータをもとに加工して、辞書ツールや、英文をいれると「適度に」日本語のルビをふってくれるものです。(この「適度」というのがポイントです。パラメータで自分の実力にあわせてチューニングできます)
ただ先にも書きましたが、ubuntu等のディストリビューションではruby-1.8が主流で、まだまだ1.9は入っていません。しかたなくrvm(Ruby Version Manager)をインストールしてruby-1.9.2をインストールしようとしましたが、従来のrubyと環境がうまく整合させるのに苦労しました。(これもenghelperがruby-1.9を前提に作ってあったためです)さらにenghelperは動作に、nokogiriとengtaggerをrubyのライブラリとして要求しており、これのインストールにも苦労しました。通常、gemを使えばそのままライブラリをインストールできるはずでした。まずgemを使うのに苦労しました。最初、どうもバージョンが古いと言われたので、以下を実行しました。

$ sudo gem update --system

しかしこれやろうとするとubuntuのシステムとぶつかる恐れがあるので推奨しない、apt-getを使えと言われました。しかたなく 以下を実行。

  sudo apt-get install rubygems

これで最新のバージョンになり、gem installができるようになりました。(それでも最新のgemよりは古いんだろうな)これでnokogiriとengtaggerがインストールできるようになりました。
nokogiriのインストール

sudo apt-get isntall libopenssl-ruby
 sudo apt-get install libopenssl-ruby
 sudo apt-get install libxslt-dev
 sudo apt-get install libxml2-dev
 sudo gem install nokogiri
nokogiri本体は、gem installでOK。動作に必要なライブラリがいくつかあり、足りないものを事前にインストールしておく。

engtaggerのインストール

sudo gem install engtagger

ところでenghelperですが、どうもDLした内容と説明があっていません。説明に書いてあるスクリプトが見つかりません。しばらく悩んでいましたが、どうも3月末から4月にかけて更新が2,3回行われたようです。現時点で最新のenghelper-0.5.7をDLしてきて内容が説明と同じものなのを確認できました。
これで辞書ツールを動かすことができました。
さすがに元のデータが用語数多い英次郎です。例題が大量にでてきます。(ただその内容については、賛否両論あるので自己責任で)
次に肝心のルビ振りツールですがうまく動きません。apacheのerror.logを見ると、engtaggerのロード(require)でロードできないとエラーがでています。engtagger, nokogiriともどうもrequire(Rubyの文法でライブラリのロードです)でエラーがでます。
> require 'engtagger'
LoadError: no such file to load -- engtagger
from /usr/share/ruby-rvm/rubies/ruby-1.9.2-p180/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'

gemできちんとインストールしたはずだが、場所を確認する。
$ gem which engtagger
エラーがでる?入ってないのか?
$ sudo gem which engtagger
/var/lib/gems/1.8/gems/engtagger-0.1.1/lib/engtagger.rb
よくわかりませんが、通常ユーザとrootで環境が違うようです。ただこの場所はどうみてもrvmが入っている/usr/share/ruby-rvm以下ではないのでまずい感じです。(rubygemsがubuntu標準なのでこの位置に入れられてしまうか?)

さらにnokogiriも調べてみます。こっちはgem which でどうしても見つかりませんが、偶然以下で見つけました。
/var/lib/gems/1.9.1/gems/nokogiri-1.5.2
どうみても場所がばらばらです。

しかたなく手作業で以下の場所にコピーしてしまいます。(irbで $LOAD_PATHを表示させてどこからライブラリを読んでいるかを調べておきました)
/usr/share/ruby-rvm/rubies/ruby-1.9.2-p180/lib/ruby/site_ruby/1.9.1

結局コピーしたのは以下のものです。

-rw-r--r-- 1 root root 25037 2012-04-06 18:41 engtagger.rb
-rw-r--r-- 1 root root  3877 2012-04-06 18:45 nokogiri.rb
drwxr-xr-x 2 root root  4096 2012-04-06 18:42 engtagger
drwxr-xr-x 7 root root  4096 2012-04-06 18:46 nokogiri
drwxr-xr-x 3 root root  4096 2012-04-06 18:46 xsd

よくわかりませんが、.rbがディレクトリ以下のものもrequireしていたんで一緒にコピーしておきました。これで無事enghelperが動作してくれました。

きちんとルビをふってくれてます。(^^)v


2012年3月21日水曜日

hadoop お試し

(何か操作を失敗したのか内容が消えてしまっていたので、記憶の限り簡単にメモしなおし)
最近話題の「ビッグデータ」のさわりを簡単に試してみたくなりました。

1.VMwareのイメージで試す
これが一番お手軽です。Yahoo! Cloud Virtual Machine Applianceここにubuntu8上に設定したhadoop-0.20があり、VMplayerを用意すれば簡単に試せます。ただシングルマシンの環境なのにセキュリティがばっちり入っていて、毎回kinitしないといけないのがめんどくさいですが。

2.Cloudera社のHP上からパッケージを落とす
Ubuntuなんか使ってる方だと簡単に環境を構築できます。リポジトリリストにCloudera社のものが入ってしまうので、通常の更新確認で若干遅くなったり、たまに反応がこなくなったりするのが玉にきずですが。(自分は結局外してしまいました)

どちらも同じサンプルプログラムが入っていて、MapReduceを実体験できます。(どうみても同じものにしか見えんw YahooでHadoop作った人が独立してCloudera立ち上げたせいなのかもしれません)当然、複数nodeの動作状態をモニタする機能もあります。(ブラウザで見ることができます)

(1)HDFSの状況を確認
HDFS上にできているファイルの確認用画面です。http://localhost:50070で見れます。(もちろんほかのマシンからでもOKです)

(2)MapReduceの動作状況の確認
今度はサンプルのPIを計算しているときの動作状況をモニタしてみます。http://localhost:50030で見れます。

最下段にRunしているジョブが表示されています。そこをクリックするとより詳細なMapReduceの状態がモニタできます。
これは見ていて非常に面白いです。ブラウザの表示を更新すると計算状況の進捗状態が直接わかります。

2011年9月18日日曜日

protocol bufferのサイズ制限について(protobuf-2.1.0)

C++のオブジェクトをシリアライズ化するのに便利なgoogle protocol buffer(PB)を使ってます。ある時、プログラムの実行記録に使っている時、以下のWarningがでてきました。


libprotobuf WARNING google/protobuf/io/coded_stream.cc:462] Reading dangerously large protocol message.  If the message turns out to be larger than 67108864 bytes, parsing will be halted for security reasons.  To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.


よくわかりませんが、PBの扱えるサイズがセキュリティ上の問題から64MBだよと言ってるらしいです。この時のログは40MBくらいでした。様子を見ると、どうも制限がかかるのは「入力」バッファのサイズのようです。(考えてみればあたりまえですが、デコードするとき一度メモリ上に読み込むはずで、そこにMAX制限がかかってます)
ソースコードを見てみましたが、ばっちりlimitが入ってました。

coded_stream.cc:

namespace {

static const int kDefaultTotalBytesLimit = 64 << 20;  // 64MB

static const int kDefaultTotalBytesWarningThreshold = 32 << 20;  // 32MB  ←警告を出すサイズまで定義されてます。


このサイズを変えればリミットと変えれるのかなと思って少し調べたら、入力ストリームに以下のメソッドがありましたが、コメントを見ると他のコードを混乱させるので、使うのはやめたほうがよさそうです。

void CodedInputStream::SetTotalBytesLimit(
    int total_bytes_limit, int warning_threshold){
  // Make sure the limit isn't already past, since this could confuse other
  // code.

ぐぐってたら、同じようなシリアライズ化のライブラリにMessagePackというのがありそちらのほうが高速だというのがありました。ちょっとそれとも比較してみると。

・制限のチェックをしているのはInputです。
→実装上、protbuf-2.1では入力データをmemcopyしてからデコードしているので64MBの制限がかかります。protbufと似たようなライブラリのMessagePackは入力の実装がmemcopyではなくポインタコピーで済ませているので入力(デコード)がprotbufより速い。しかし最新のprotbuf-2.4.1のInputの実装を調べてみたらmemcopyからポインタコピーになっていました。
2.1で制限として定義されていた変数名も2.4.1ではなくなっており、おそらく64MBの入力サイズの制限はないと思います。(これ間違いだった!!)

・しかし物理的な上限は発生します。
入力チェックのメソッドと思われるところで、入力ポインタ(current_point)のチェックをしているところがあり、真っ先にINT_MAXと比較しています。
当たり前ですが、ポインタはメモリアドレスを表わしていますから最大の値はINT_MAXになります。
またやっかいなことにこのINT_MAXは当該ライブラリが実行されるOSのアーキテェクチャに依存します。
つまり32bit OSの場合には物理的に4GBが最大になります。(ロギング等、実行は64bit linuxでやり、読み込みが32bit Windowsのプログラムなんかだったりするとうっかりこの制限にひっかるので注意!)

まあ、こんな大きなサイズのファイルをめったに作成しないでしょうが念のため


実験してみました。単純に同じデータをどんどん追加していく試験プログラムを作ります。(ファイル名:tttに追記していきます)


[ examples]$ ./add_person_cpp ttt
[ examples]$ ls -l ttt
-rw-r--r-- 1 hoge hoge 29561520  9月 16 15:50 ttt ←tttのサイズはまだ32MB未満です
[examples]$ ./list_people_cpp ttt|tail
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
Person ID: 200
  Name: rinrin
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
Person ID: 200
  Name: rinrin
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
[examples]$ ./add_person_cpp ttt
[examples]$ ls -l ttt
-rw-r--r-- 1 hoge hoge 33661520  9月 16 15:53 ttt ←tttのサイズが32MBを超えました!
[examples]$ ./list_people_cpp ttt|tail
libprotobuf WARNING google/protobuf/io/coded_stream.cc:462] Reading dangerously large protocol message.  If the message turns out to be larger than 67108864 bytes, parsing will be halted for security reasons.  To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
Person ID: 200
  Name: rinrin
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
Person ID: 200
  Name: rinrin
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
[examples]$

読み込むファイルが32MBを超えると、warningがでました。(ただ、まだ正しく読めてます)
またこれ以降、追記時に一度読まないといけないのでwarningがでるようになりました。

[examples]$ ./add_person_cpp ttt
libprotobuf WARNING google/protobuf/io/coded_stream.cc:462] Reading dangerously large protocol message.  If the message turns out to be larger than 67108864 bytes, parsing will be halted for security reasons.  To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.

ただ、まだ正しく読めます。


新しいprotobuf-2.4.1を試そうと考えたが、そもそもprotobufの実装が違うんだからlib内の関数のエントリー名を違えば、protocが生成するヘッダーだって異なってくるのが当たり前。(libだけすげかえれば・・・・なんて甘いことはだめだった Orz)
以下のように強引にヘッダーの場所やライブラリの場所を、2.4.1に指定して(システムにはすでに2.1.0がインストールされていますが、まだこの形態をVerUpしたくない)リビルドします。

[examples]$ c++ list_people.cc addressbook.pb.cc -I/opt2/home/maeda/protobuf-2.4.1/src -lpthread -L/opt2/home/hoge/protobuf-2.4.1/src/.libs -lprotobuf -o list_people_cpp

また実行前にLD_LIBRARY_PATHを追加して、2.4.1のdllを認識させます。

[examples]$ ldd ./list_people_cpp
        libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003a76000000)
        libprotobuf.so.7 => not found ←認識できていない!!
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000003a7b800000)
        libm.so.6 => /lib64/libm.so.6 (0x0000003a75800000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003a7b400000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003a75400000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003a73c00000)
[examples]$ echo $LD_LIBRARY_PATH
/opt/intel/Compiler/11.0/084/lib/intel64:/opt/intel/Compiler/11.0/084/ipp/em64t/sharedlib:/opt/inte\
l/Compiler/11.0/084/mkl/lib/em64t:/opt/intel/Compiler/11.0/084/tbb/em64t/cc4.1.0_libc2.4_kernel2.6.\
16.21/lib:/opt/intel/Compiler/11.0/084/lib/intel64:/opt/intel/Compiler/11.0/084/ipp/em64t/sharedlib\
:/opt/intel/Compiler/11.0/084/mkl/lib/em64t:/opt/intel/Compiler/11.0/084/tbb/em64t/cc4.1.0_libc2.4_\
kernel2.6.16.21/lib:/usr/local/lib:/usr/local/lib64:/opt2/qtcreator-2.0.1/lib:/usr/local/lib:/usr/l\
ocal/lib64:/opt2/qtcreator-2.0.1/lib
[examples]$ setenv LD_LIBRARY_PATH /opt2/home/hoge/protobuf-2.4.1/src/.libs:${LD_LIB\
RARY_PATH}
[examples]$ ldd ./list_people_cpp
        libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003a76000000)
        libprotobuf.so.7 => /opt2/home/hoge/protobuf-2.4.1/src/.libs/libprotobuf.so.7 (0x00002ad95\
96e8000) ←dllを認識した!!
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000003a7b800000)
        libm.so.6 => /lib64/libm.so.6 (0x0000003a75800000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003a7b400000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003a75400000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003a73c00000)
        libz.so.1 => /usr/lib64/libz.so.1 (0x0000003a76400000)
[examples]$

試してみたらダメ!!同じwarningが出ます。2.4.1の実装を調べたら、2.1.0から消えた64MBの閾値を格納する変数が別の位置にしっかり定義されていました。

    google::protobuf::io::CodedInputStream decoder(&input);  // has initializer but incomplete type
    decoder.SetTotalBytesLimit(100000000, 60000000);

とやって強引にlimitを変更しようとしましたが、最初のdecoderを宣言するところでコンパイルエラーがでます。(引数に入れているインスタンスがinitializerを必要としていると、言ってきました)
どうもコメントにあるように、このメソッドは使っちゃだめなようです。(googleのところも散々調べてみましたが、特殊な入力の時だけこのSetToalBytesLimit( )は働くようですが、それでもunusualだと書いてました)

PBの最初のところに、「これはRPCのデータを送るために作られた」、「ネットワークで大きなサイズのデータを送るのは問題がおきる」と書いてありましたので、PBをログ記録に使うのはそもそも用途が間違っているようです。