C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

openSUSE-12.2 + Razor-qt

と、言う訳で

入れてみました。

f:id:USAGI-WRP:20121212192221p:plain

初期状態で気に入らない事と言えば、キーリピートの待ちが長い事と、PrintScreenキーに割り当てが無い事くらいなものですが、何れも設定可能なので大丈夫。

デスクトップ環境KDEをデフォルトでPlasmaをフルセットで使うにはやはり重たさがある。スクリーンショットはそれなりのマシンパワーがあるPCなのでWMはKwinのままだけど、Atom N445のネトブさんはRazor-qt+Openboxかな。

蛇足

久しぶりにスクリーンセーバーの設定を眺めていて、ついつい笑ってしまうのがBSOD。

f:id:USAGI-WRP:20121212192240p:plain

ふふふ(*´ω`*)

cpp_ac_2012_4th の particle_system そのもののベンチマーク; time, gprof, oprofile

Introduction

さて、先日書いた particle_system について最適化を進めてみようと思います。その為には particle_system 自体について、どこが全体の動作に対してボトルネックとなっているのかなど知らないと闇雲で場合に寄っては無駄な最適化を試みてしまうかもしれません。そんな訳で、今回は particle_system のベンチマークを行います。用語としてはベンチマークというよりはプロファイリングですね。

Contents

  1. particle_system::updateをN回ループしてtimeで計る; 最も単純で簡単な性能評価
  2. 1.を gprof で計る; プロファイリングツールの活用
  3. OProfile で計る; 高機能プロファイリングツールを試す

1. particle_system::updateをN回ループしてtimeで計る; 最も簡単にできる単純な性能評価

source

何という事は無い、単に particle_system を生成し、 particle_system::update を コマンドライン引数で指示された回数だけ呼び付けるだけ。

工夫があるとすれば、コンソールが「今何回目」で溢れても面白く無いのでエスケープシーケンスを用いて、「今何回目を出力」、「改行+フラッシュ」、「1つ上の行へ移動」とかstd::coutに流しているくらい。本題にはほぼ関係無い。ほぼ、と言うのは、標準出力への出力コストが厳密な particle_system それ自体のベンチマークに対しては余計なコストであり測定に厳密には誤差を生じる原因となる点だが、今回はそもそも明らかに particle_system::update の負荷の方が大きいので、視覚的な実行状況の分かりやすさの為に出力を設けた。

もののついで、 Makefile を用意したので、

% make
 ...

と、すれば o3 o2 o1 o0 の各実行ファイルが生成される。それぞれ -O3 -O2 -O1 -O0 最適化レベルの実行ファイルである。

time コマンド

GNU timeに任意のコマンドを指示すると、その実行結果について簡単なプロファイルを得られる。

実は time コマンドは2種類ある。正確に言えば/usr/bin/timeなるコマンドが1つ、もう1つは伝統的にシェルが内蔵するtime内部コマンド。シェル内蔵のtimeが普通は優先される。

  • シェル内蔵版time
% time sleep 1
sleep 1  0.00s user 0.00s system 0% cpu 1.005 total
  • /usr/bin/time
% /usr/bin/time sleep 1
0.00user 0.00system 0:01.00elapsed 0%CPU (0avgtext+0avgdata 3072maxresident)k
0inputs+0outputs (0major+239minor)pagefaults 0swaps

実は /usr/bin/time には --verbose オプションがあり、有効にするとこれだけでも優秀なプロファイラーを手に入れた気がしてくる。

% /usr/bin/time --verbose sleep 1
        Command being timed: "sleep 1"
        User time (seconds): 0.00
        System time (seconds): 0.00
        Percent of CPU this job got: 0%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.00
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 3072
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 240
        Voluntary context switches: 2
        Involuntary context switches: 2
        Swaps: 0
        File system inputs: 0
        File system outputs: 0
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

さて、紹介しておいて何だが、今回はシェル組み込み版のtimeで十分だったりする・x・
/usr/bin/time が気に入った方があればaliasでも貼っておくと良いだろう。

result
% time ./o2 1000
./o2  34.52s user 0.01s system 99% cpu 34.587 total

と、言う訳で 34.52 秒/update×1000回掛かったらしい。つまり、 34.52 ms/update 。勿論この数値は測定環境によって異なるので、正しくは単位系は ms/update-{the machine} という事になる。

particle_systemは開始から粒子数が particle_system::num_of_initial_particles 個になるまで particles::generator_timer_time 時間毎に粒子を1つ生成する。具体的な現在の実装から言えば、粒子数256個に至るまで25ms毎に1つ粒子を生成する。粒子数が最大となるのは6,400ms、6.4秒後となる。それまでは徐々に負荷が上昇し、それから計算コストは一定となる。実際、この particle_system_tester を実行すると開始直後のカウントアップの勢いが徐々に定常状態となる様を感じられると思う。

さて、残念ながら time で知る事ができるのはこの程度(全体のメモリー消費とかも分かるけど…)である。

とは言えこれだけでも考察してみると、1ループ辺りの平均計算時間が 34.52ms では 28.97 frames/sec と言う事になり、 particle_system は現状でも 60 FPS には遠いが、 30 FPS 程度の性能はこの計算機においては出せていた事は分かる。つまり、実際には 6.8 FPS 弱(≈ 147.1 ms/frame)に留まっていたボトルネックのうち、もしかしたら particle_system::updateの負荷は 34.52/147.1 ≈ 23.47% 程度なもので、残り76.53%は別の原因ではないかという推測はできる。

とは言え、この推測は Native-client と通常のPCとを比較しており、計算機が違い、naclのPCに対するオーバーヘッドがどれほどか正しく評価しなければ単純に次元を加減算して良いものではない。

…実際問題から言うと、恐らく nacl --(JSON)--> JS のインターフェースが激烈に重くなっている主因な気はするが…。何せ 160×90=1440 個のR8G8B8の文字列情報を投げ付けて、それをJS側でJSON.parseしてオブジェクトに復元している。更にその後にcanvasのcontextに矩形描画命令を160×90回発行するループがある。そこは今回は測っていないので現段階では直感でしか無いが、恐らく負荷の76.53%の4/5ほどJSONインターフェース、残りが描画処理ではないかと思う。次回はC++の記事の本論ではないが、JS側のプロファイリングも行いたい。

2. 1.を gprof で計る; プロファイリングツールの活用

さて、ゴチャゴチャ書いたものの、結局のところ、 particle_system の内部のボトルネックは time だけでは分からない。ここで少し前ならば バグベアード - extra - C++ - TrickLibrary を紹介したかったのだけど、C++11環境ではどうも上手く召喚できない。

1つの方法として、プロファイリング対象としたいスコープでロガーを仕掛ける事も考えられるが、ソースコード全体にロガーを仕掛けて回るのは面倒なのでBugbeardの様な悪魔が居なければちょっと自分でちまちまロガーを仕込むのは面倒。

と、言う訳で安直にGNU gprofする。g++のオプションに`-pg`を追加して、それを実行して出力される gmon.out を gprof に掛ければプロファイルが見れる…はずだった。

Makefileにgprof用のオプションも用意しておいたので、`make o2-gprof`とかするとプロファイル作成まで全自動という仕掛けはして置いた。Makefileには以下の様に o0-gprof ほか o1, o2, o3 について用意した。o0の場合のみ負荷が尋常じゃないのでループ回数は100回、他は1000回にしてある。

o0-pg:
  g++ -std=c++11 main.cpp -o o0-pg -pg -O0

o0-gprof: o0-pg
  ./o0-pg 100 && mv gmon.out o0-gmon.out && gprof ./o0-pg o0-gmon.out > o0-gprof.log

`make o2-gprof`でビルド、実行、gprofによるプロファイル作成まで行う。

% make o2-gprof

しかし、どうやら私の試す環境では、O2最適化した場合gprofではまともにプロファイリングできない様で、役に立つ結果がまるで出なかった。

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  Ts/call  Ts/call  name    
  0.00      0.00     0.00        1     0.00     0.00  _GLOBAL__sub_I_main
  0.00      0.00     0.00        1     0.00     0.00  std::vector<std::vector<WonderRabbitProject::particle_system_tester::particle_system::pixel, std::allocator<WonderRabbitProject::particle_system_tester::particle_system::pixel> >, std::allocator<std::vector<WonderRabbitProject::particle_system_tester::particle_system::pixel, std::allocator<WonderRabbitProject::particle_system_tester::particle_system::pixel> > > >::_M_default_append(unsigned long)

まるでプロファイリングできていない。この2件のエントリーしか取れていないので役に立たない。たぶん setitimer システムコールを使って簡単なプロファイラを作る - bkブログ に書いてあるタイマー都合なのかと思う。

ちなみにo0の場合はそれなりにプロファイルが取れる。

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
  6.73      2.85     2.85      100    28.50   422.60  WonderRabbitProject::particle_system_tester::particle_system::update_pixels()
  5.84      5.32     2.47 218160202     0.00     0.00  WonderRabbitProject::particle_system_tester::particle_system::vector2::vector2(WonderRabbitProject::particle_system_tester::particle_system::vector2 const&)
  5.69      7.73     2.41 290928961     0.00     0.00  boost::operators<WonderRabbitProject::particle_system_tester::particle_system::vector2, WonderRabbitProject::particle_system_tester::particle_system::vector2>::operators()
  5.20      9.93     2.20 290928961     0.00     0.00  boost::totally_ordered<WonderRabbitProject::particle_system_tester::particle_system::vector2, boost::integer_arithmetic<WonderRabbitProject::particle_system_tester::particle_system::vector2, boost::bitwise<WonderRabbitProject::particle_system_tester::particle_system::vector2, boost::unit_steppable<WonderRabbitProject::particle_system_tester::particle_system::vector2, boost::detail::empty_base<WonderRabbitProject::particle_system_tester::particle_system::vector2> >, boost::detail::empty_base<WonderRabbitProject::particle_system_tester::particle_system::vector2>, boost::detail::true_t>, boost::detail::empty_base<WonderRabbitProject::particle_system_tester::particle_system::vector2>, boost::detail::true_t>, boost::detail::empty_base<WonderRabbitProject::particle_system_tester::particle_system::vector2>, boost::detail::true_t>::totally_ordered()
  4.84     11.98     2.05 290928961     0.00     0.00  boost::totally_ordered1<WonderRabbitProject::particle_system_tester::particle_system::vector2, boost::integer_arithmetic<WonderRabbitProject::particle_system_tester::particle_system::vector2, boost::bitwise<WonderRabbitProject::particle_system_tester::particle_system::vector2, boost::unit_steppable<WonderRabbitProject::particle_system_tester::particle_system::vector2, boost::detail::empty_base<WonderRabbitProject::particle_system_tester::particle_system::vector2> >, boost::detail::empty_base<WonderRabbitProject::particle_system_tester::particle_system::vector2>, boost::detail::true_t>, boost::detail::empty_base<WonderRabbitProject::particle_system_tester::particle_system::vector2>, boost::detail::true_t> >::totally_ordered1()
 ...

それなりというか大量に取れている。とりあえず vector2::vector2(const vector2&) 呼び過ぎって事は確かな様だけど、実際それはコンパイラーの最適化で割と消えている気はする。しかし比較対象のプロファイルも取れていないので、なんとも。

3. OProfile で計る; 高機能プロファイリングツールを試す

と、言う訳で gprof ではまともにプロファイルを取れないので OProfile - A System Profiler for Linux (News) を試す事に。openSUSE-12.2のリポジトリーにもエントリーがあるので `zypper in oprofile` などしてさっくり入れる。

ちなみにOProfileについてはopenSUSEの日本語ドキュメントがあった。

さて、この手順も参考にしつつ、差し当たり`alias opcontrol="sudo opcontrol"`とかaliasも張りつつ、

% cp /boot/vmlinux-`uname -r`.gz /tmp
% gunzip /tmp/vmlinux*.gz
% opcontrol --vmlinux=/tmp/vmlinux*

これは問題なく出来た。続いてstartを試みるとエラーに遭遇してしまった。

% opcontrol --start
Using default event: CPU_CLK_UNHALTED:100000:0:1:1
Error: counter 0 not available nmi_watchdog using this resource ? Try:
opcontrol --deinit
echo 0 > /proc/sys/kernel/nmi_watchdog

実はちょっと良く分かって居ないが、とりあえずエラーと共にお試しあれと表示のある通りにすると、

% sudo su
% opcontrol --deinit
% echo 0 > /proc/sys/kernel/nmi_watchdog
% exit

以降は start 可能になった。

% opcontrol --start
Using 2.6+ OProfile kernel interface.
Reading module info.
Using log file /var/lib/oprofile/samples/oprofiled.log
Daemon started.
Profiler running.

早速測る。

% opcontrol --reset && ./o2 1000 && opcontrol --stop
Signalling daemon... done
Stopping profiling.

dumpしてopreportを出力してshutdownしとく。

% opcontrol --dump
% opreport ./o2 > o2-op.profile
% opcontrol --shutdown
Killing daemon.

レポートを見る…(´・ω・`)

% cat o2-op.profile
CPU: AMD64 family10, speed 3e+06 MHz (estimated)
Counted CPU_CLK_UNHALTED events (Cycles outside of halt state) with a unit mask of 0x00 (No unit mask) count 100000
CPU_CLK_UNHALT...|
  samples|      %|
------------------
  1046820 100.000 o2
        CPU_CLK_UNHALT...|
          samples|      %|
        ------------------
          1046801 99.9982 o2
               19  0.0018 [vdso] (tgid:7443 range:0x7ffff53ff000-0x7ffff5400000)

(´・ω・`)

`opannotate`も試してみる。

% opannotate -s ./o2-g > o2-op.annotate
% cat o2-op.annotate
 ...

こちらではソース中と対応付けてどこが何回呼ばれてどれほどのCPU時間を要したのか表示が出ていた。例えば、

               :  struct color_type{
               :    double r, g, b;
               :  public:
               :    color_type& operator+=(const color_type& t)
 52615  5.0248 :    { r += t.r; g += t.g; b += t.b; return *this; }
               :    
               :    color_type operator*(const double v)
 86370  8.2485 :    { return { r * v, g * v, b * v }; }

とか。

oprofileについてもMakefileに仕込んで置きました。`make o2-op`とかすると、-g付きのビルド、oprofileを開始、実行、oprofileを停止、oprofileからdump、opreportとopannotateを実行、と一連の操作を自動的に行います。`make oprofile`とかすると -O3 -O2 -O1 -O0 について全自動で処理します。

o2-g:
	g++ -std=c++11 main.cpp -o o2-g -g -O2

o2-op: o2-g
	sudo opcontrol --start && sudo opcontrol --reset
	./o2-g 1000
	sudo opcontrol --stop
	sudo opcontrol --dump
	sudo opcontrol --shutdown
	opreport --demangle smart ./o2-g > o2-op.profile
	opannotate -s ./o2-g > o2-op.annotate

…と、本来であればプロファイリングからボトルネックをどうのこうの〜としようと思っていましたが、今日はもう遅いので、このプロファイルからどうのこうの〜はまた今度にして寝ます〜おやすみなさーいzz

QtCreator-2.5.0 + QtOpenGL + GLUT ==> teapot

(いちおう)エディターたるVimから、IDEたるQtCreatorでの開発を勉強中。先ずはお約束の第一歩、teapotの備忘録。

プロジェクトをQtの雛形から作成

  1. File --> New file or Project (CTRL+N)
  2. Projects: Applications --> Qt Gui Application

以降、仮にプロジェクト名称を"hoge"としたものとして進める。

ウィザードに従い、特に困る事は無く、

  • hoge.pro
  • Headers
    • mainwindow.h
  • Sources
    • main.cpp
    • mainwindow.cpp
  • Forms
    • mainwindow.ui

が生成される。バージョン管理システムとしてgitを仕込むのもプロジェクト作成時にまとめてできる。

この時点で既にビルド&実行してもフォームが見えるだけのアプリが立派に動作してくれる。

QtOpenGL を組み込む

どうやら Qt で OpenGL を扱う標準的な方法として、 QtOpenGL なるAPIがあるらしい。

プロジェクトに QtOpenGL を使う為の定義を施す。

hoge.pro

.pro はどうやら QtCreator におけるプロジェクトファイル、プロジェクトのソースコード以外のアレコレを定義するファイルらしい。この.proに

QT += opengl

を加える。どうも見るからにMakefile系のスクリプト的な感じで変数を定義しているらしい。この QT なる変数はQt系のライブラリーのリンクを制御したり、UIデザイナーでの挙動に関わっているのではないかと思う。

これでこのプロジェクトでは QGLWidget などの QtOpenGL 周りのライブラリーが使用可能になる。

ついで C++11 を使いたければ、

QMAKE_CXXFLAGS += -std=c++11

も追記しておく。

QGLWidget を実装

  1. File --> New file or Project (CTRL+N)
  2. Files and Classes: C++ --> C++ Class

などして QtOpenGL による Qt の OpenGLWidget (フォーム部品)を実装する。

"gl_widget"なるクラスを QtCreator で作成したとして進める。差し当たり次の様に OGLWidget を継承して gl_widget を実装する。

gl_widget.h
#ifndef GL_WIDGET_H
#define GL_WIDGET_H

#include <QtOpenGL>

class gl_widget: public QGLWidget
{
  Q_OBJECT
public:
  gl_widget(QWidget*);
protected:
  virtual ~gl_widget();
  virtual void initializeGL();
  virtual void resizeGL(int, int);
  virtual void paintGL();
};

#endif // GL_WIDGET_H

Q_OBJECT は Qt 関連品にはとりあえず付けておけば問題無い様だ。今回の本題でも無いので今回はとりあえず付けて置くでスルー。

Qt Note: Q_OBJECT を付けるべきか

includeガードがいつも#pragmaな癖にとかは今回QtCreatorの雛形からできるだけそのままにして居るので気にしないで戴きたい。

gl_widget.cpp
#include "gl_widget.h"

gl_widget::gl_widget(QWidget* p = nullptr)
  :QGLWidget(p)
{}

gl_widget::~gl_widget(){}

void gl_widget::initializeGL(){
  glClearColor(.0, .0, .0, 1.);
}

void gl_widget::resizeGL(int x, int y){
  glViewport(0, 0, x, y);
  glLoadIdentity();
  auto rx = x / 200.;
  auto ry = y / 200.;
  auto z = 1;
  glOrtho(-rx, rx, -ry, ry, -z, z);
}

void gl_widget::paintGL(){
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  //glutWireTeapot(0.5);
}

いつもは .hxx とかしてこの程度なら実装もヘッダーに詰め込む癖にー、とかも気にしないで戴きたい。あとファイル名とか。これらも基本的に今回は QtCreator の生成した雛形そのままです。

initializeGL/resizeGL/paintGL は QGLWidget に定義された仮想関数で、初期化/サイズ変更/描画のタイミングでよしなに呼ばれてくれます。素のGLの各種コールバック"的"なものですね。

paintGLで`// glutWireTeapot(0.5);`がコメントアウトしてありますが、これはまだ GLUT の準備をしていない為に使用できないからです。それでも実行すれば glClearColor で設定した色で glClear により描画タイミング毎に画面がクリアされるので動作は確認できるでしょう。

でもまだビルド&実行しても真っ黒なGLの描画画面は現れません。次にこの実装した Qt のウィジェットをフォームに組み込みます。

mainwindow.ui へ gl_widget を組み込む

mainwindow.ui を QtCreator で開くとソースではなくグラフィカルなUIデザイナーが表示されます。標準ではフォームの表示されている右側にオブジェクトをツリー表示している部分が出ているので、そこで MainWindow の中の centralWidget のコンテキストメニューを開き、 `promote to` しましょう。

Promoted class name に gl_widget と先ほど実装したQtのOpenGLウィジェットを書き込むと、自動的に Header file も gl_widget.h と入力の済んだ状態になります。 Add して Promoted Classes に表示が出たならば、ビルド&実行してみましょう。

f:id:USAGI-WRP:20121209151323p:plain

先ほど実装した gl_widget が無事に動作すると真っ黒でクリアーするだけのGLの小窓が出ます。

GLUT ==> teapot

続いて GLUT を QtCreator のプロジェクトのお作法で準備します。

hoge.pro
LIBS += -lglut

身も蓋もない気がしますが、変数LIBに -lglut とか GCC/clang のライブラリーリンクオプションを加えます。

main.cpp
#include <QApplication>
#include <GL/glut.h>
#include "mainwindow.h"

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

  QApplication a(argc, argv);
  MainWindow w;
  w.show();
    
  return a.exec();
}

`GL/glut.h` のインクルードと `glutInit(&argc, argv)` を追加しました。他は QtCreator で生成した雛形のままです。

gl_widget.h
#include <GL/glut.h>

追加しておきましょう。

gl_widget.cpp
  glutWireTeapot(0.5);

コメントアウトしてあった teapot の描画コードのコメントアウトを外しましょう。

ビルド&実行すると、

f:id:USAGI-WRP:20121209152149p:plain

GLUTお約束の teapot が描画されました (*´ω`*)

openSUSE-12.2にRictyフォントを導入する際の注意

Abstract

openSUSE-12.2環境に置いて`./ricty-generator.sh auto`はシステムに導入されたInconsolata/M+ 1M regular/M+ 1M boldの各フォントファイルを見つけてくれない。

% ./ricty-generator.sh auto
 ...
Error: ... not found

手作業で各フォントを指定する必要がある。更に言うとopenSUSE-12.2のInconsolata/M+ 1M regular/M+ 1M boldの各フォントのファイル名はricty-generator.shの想定とは異なるので注意を要する。

Method

1. Inconsolata 導入

% zypper in inconsolata
 ...

※ inconsolata または google-inconsolata-fonts → software.opensuse.org: パッケージにより /usr/share/fonts/truetypeに導入されるファイル名が異なる場合もあるので注意。

2. M+ 導入

% zypper in mplus-fonts

3. ricty-generator.sh

% git clone git@github.com:yascentur/Ricty.git
 ...
% cd Ricty
% ln -s /usr/share/fonts/truetype/inconsolata.otf Inconsolata.otf
% ln -s /usr/share/fonts/truetype/mplus-1m-regular.ttf migu-1m-regular.ttf
% ln -s /usr/share/fonts/truetype/mplus-1m-bold.ttf migu-1m-bold.ttf
% ./rictry-generator auto

inconsolata.otfではなくInconsolata.otfにしておく辺りに特に注意。

4. checkinstallでrpm化し導入

install用にMakefileを書く。

% gvim Makefile
 ...
all:

install:
        install -m 644 *.ttf /usr/share/fonts/truetype

checkinstallでrpmにする。

% sudo su
% checkinstall
 ...

rpmを導入する。

% rpm -i /usr/src/packages/RPMS/x86_64/Ricty-20121208-1.x86_64.rpm
Alternative

上記 3. では`ln -s`により作業ディレクトリーに合成元のフォントファイルのリンクをricty-generator.shにautoオプションで都合の良い様に作成したが、autoオプションに寄らずコマンドラインで設定しても構わない。

% ./ricty-generator /usr/()/inconsolata.otf /usr/()/mplus-1m-regular.ttf ()-bold.ttf

After care: C++ Advent Calendar 2012 / day 4th : Native-client vs. HTML5 ; C++ in the web-client-world!

先日のC++ Advent Calendar 2012 / day 4th : Native-client vs. HTML5 ; C++ in the web-client-world! - C++ ときどき ごはん、わりとてぃーぶれいく☆の続き、その1です。

ちょっとだけリファクタリングしたり、C++とJSのインターフェース部分のやっつけ感をどうにかするなどしました。

f:id:USAGI-WRP:20121207194935p:plain

特に意図して居ませんでしたが、どうやらSCOREも上昇した様です^-^
(粒子群や画素群の更新がリファクタリングに伴い少々コンパクト&スマートになった影響かな。)

主な変更点
  • パーティクルシステムの定義をnative-clientのインスタンス定義用のinstance.hxxからparticle_system.hxxへ分離
  • JS-->C++のインターフェースをやっつけ文字列コマンドからJSONに変更
  • particle_system内のparticle及びpixelの状態更新をparticle/pixelそれぞれの内部ではなく、particle_systemから行う様に変更。particle/pixelはparticle_systemに対しfriend。
  • スクリーンと描画空間の分解能の任意設定を実装。一部マジックナンバーとなっていた分解能に関する決め打ちの値を任意に設定可能になりました。
    • JS側にdebug_command.change_resolutionなる関数を実装したので、ブラウザーの開発者ツールのJSコンソールから debug_command.change_resolution(640,480,64,48) を評価すると、スクリーン分解能VGA、画素分解能64x48(画素サイズ10x10)に切り替えるなど可能になりました。

この後は

次はパーティクルシステムの効率化かな。

ちなみに、実は当初HTML5(pure)版を書く段階で非常に重いベンチマークになる事は想像できますので、「全ての画素が全ての粒子からの影響を計算する」のではなく、「粒子から一定距離の画素にのみ粒子の影響を計算する」なる実装にしていました。

例えば、前者の場合は、ある粒子が(x,y)に存在した時、画素空間の分解能が160x90であれば14.4k個の画素に対して粒子からの距離に基づいた色(光)の影響具合を計算する事になりますが、後者の場合には粒子の半径rまたはrに影響量を増減する調整用の定数を掛けたr'を用意し、仮に之が40画素分程度の距離であれば、πr'r'=3.14…×40×40≈5,027個の画素だけに粒子の影響を計算すれば良い事になり、負荷を大きく下げられます。

実際には、画素群は特別なデータ構造を持たせない限り、描画系APIとの整合性もあり、通常は離散的な2次元のユークリッド空間(X軸とY軸に四角い升目状に考える)で管理していますから、粒子の座標の周囲を円形に列挙する事は少し難しく、先ずは粒子の影響範囲が外接する矩形領域を切り出し、列挙コストと画素の処理コストを天秤に掛けて、画素と粒子の距離を計算するか、或いは綺麗な円形ではなく矩形領域をそのまま粒子の影響範囲の計算対象として処理を行います。

しかし、今回実装したパーティクルシステムでは、結局は画素の光り具合の決定に全ての粒子との、いわば総当りの計算を行なっています。光り具合のモデルを、{(I)ntensity}={c_(l)uminance}/({(d)istance}+1)、但しI:光りの強さ、l:光り具合調整用の定数、d:画素に影響を及ぼそうとする粒子までの距離、としつつ、lを大きめにし、光り溢れつつも飛び回る粒子感もある眩しくて綺麗な…とかやっているうちに、ベンチマークだし総当りで計算した方が綺麗な絵が簡単に出るし良いかな〜と、そんなノリで総当りの実装になっています^^;

と、まあそれはさておき、粒子から画素群の列挙であれば上述の通りで、ゆっくり整理して考えれば矩形領域の画素群の切り抜きは簡単に想像できますね。では逆ならばどうでしょう?つまり、ある画素が主体となり、周辺にある粒子を列挙し、それらからの影響を計算するとしたなら。

粒子群は勿論動きにその粒子群の存在する世界の物理法則は適用できますから、実際完全に予測したり、事前に計算しておく事も可能です。乱暴に片っ端から事前計算してしまってデータベース化しておくか、或いはオイラー法よりも高度な精密な計算を行い高精度に予測する必要がある、でしょうか?

列挙は可能だがそれぞれの座標が予測できない粒子群についてある座標の近くに居る粒子を効率的に抽出できる様に工夫したデータ構造があります。代表例は任意の矩形領域に粒子群を分割して記憶する事をベースとし、矩形領域からのピックアップを得意としたRectangler-tree、またその円バージョンの派生データ構造、或いは対象空間を2分割を繰り返してそれぞれに含まれる粒子群を記憶したり、その分割比率を任意にできる様にしたりするkd-tree系のデータ構造など、モデルに応じて粒子群のデータ構造を最適化しておく方法もあります。

実はこの後、このベンチマーク用のパーティクルシステムをどのように効率化してこのシリーズを進め様かはまだ考えていません^^; もちろん、マルチスレッディングやSIMD(NaClで動くかなぁ…)周りなどの実用的な力技やシェーダーを使ってしまうチートも含め、もうちょっと年内にこの記事の続きも書きたいと思っています。

ではみなさん、良いお正月を〜^^(ぉ

C++ Advent Calendar 2012 / day 4th : Native-client vs. HTML5 ; C++ in the web-client-world!

C++ Advent Calendar 2012 / 4th day
  • (!) この記事はC++ Advent Calendar 2012の参加記事です ヽ(=´▽`=)ノ
    • 記事の公開と本編内容が4th dayのリミットより数時間遅れてしまいました事をお詫び申し上げます。

Native-client vs. HTML5 ; C++ in web-client-world!

諸元
  • Published: 2012.12.4
  • By: Usagi Ito <usagi@WonderRabbitProject.net>
  • Environments: (to see the last section of this entry)

Abstract

世間ではHTML5が一般にも話題になり始め、徐々に持て囃される様になり、次第に一般にも浸透して来た様な気がする2012年のクリスマスシーズン、

(…中略…)

ウェブはコンパイルされるべきである( ・`ω・´)

と、言う訳でコンパイルを恐れるじゃぱすくりぷたーどもをC++ぱわーでひれふしsdpgこrthkよりネイティブにアプローチを掛けつつもJavaScriptの柵に囚われたHTML5に対しNative-clientの優位性を示すと共にC++erにC++のウェブクライアントのお仕事も下さいフラッシャーなにそれ変態なのHTML5にプラスしてNative-clientを協調的に動作する様に組み込みよりパワフルな底力と表現力の可能性を示したい。

Introduction

Native-client

Native-clientはウェブブラウザー上でネイティブコードアプリケーションを実行する為の枠組みで、 Chromium(≈Chrome) 14 からブラウザーへの実装が進められ執筆時点最新の Chromium 24 でも勿論使用可能です。

一般に従来ウェブブラウザー上でアプリケーションを動作させる方法は、FLASH、JavaアプレットJavaScript、その他のマイナースクリプト環境などが考えられますが、何れもクライアントのマシン性能を生かし切るには高級過ぎたり、マルチスレッディングやリアルタイム性能に欠ける、実行環境が一般的でない、などしています。尤も最後の実行環境の一般性についてはNative-clientは潜在的にはChromeのシェアを以って普及していると言うのはやや乱暴な状況ではありますが、Chromeウェブストアから既にSecure ShellLime Berta(NaClテスト版)Bullet Physics NaCl TestOgre Sample Browser NaCl、などNative-clientを用いたサンプルや実用アプリの開発を見ることができます。と、いうかNative Clientのクオリティー高すぎ!家庭用ゲーム機と同クオリティーを実現したブラウザゲームまとめ | Chrome Lifeとか実際とてもゲームです。

HTML5

みんな大好きHTML5。WHATGが"Living Draft"として永遠に完成させない(もちろん良い意味で表現している)方針を取りながらW3Cとやんやしつつ成長中の新型HTML関連規格。"関連"と付けたのは、既に之が純粋なHTMLというデータ記述言語の枠を超えて、CSSは勿論の事、スクリプト言語たるJavaScriptや、通信のリアルタイム性や接続性を高めるWebSocket、ウェブとマルチメディアデバイスとのHTMLによる接続が実現しつつあるWebRTCなど、ほか細かい仕様もたくさん(正直全部把握してない)。

しかし、HTML5プログラマビリティはJavaScriptだけに掛かっている。JavaScriptは2014年に次の規格を発表する事になっていた気はするが、この言語はここ10年、規格の更新云々でもめ続けて今に至っている。不安は大きい。

HTML5で追加された今回も扱うcanvas、それにvideoやaudioの制御は勿論の事、WebGL(≈OpenGL)、WebCL(≈OpenCL)に至ってもそのAPIJavaScriptのみを大前提としている。実に馬鹿げている。ブラウザーはスクリプト言語の処理系を切り離し、ユーザーに多様なスクリプトサポートを提供する枠組みを整備すべきではなかろうか(例えば<script type="application-x/haskell">とかもっと手軽にできて欲しいし、Native-clientやそこまでいかなくともJavaScriptではなくPythonRubyなどももっと手軽に使えても良いのではなかろうか、速度的にはJavaScriptの方がマシらしいけど)。

JavaScriptは変態的な最適化が施されているであろう処理系V8を以ってしても、所詮はJavaScript、少なくとも現在のJavaScriptの言語仕様では高速化やよりネイティブな仕組みの実装に対し柵が大きく、そもそもイベント駆動に最適化され、まともなスレッディングやリアルタイム要求に答えらず、データ構造やメモリーの取り扱いも不自由なJavaScriptでなんでGLだのCLだの…無理矢理過ぎる。

そこで、C++、Native-clientの出番です( ・`ω・´)

Contents

前置きが長くなりましたが、今回の記事で取り扱う内容について整理します。

  • HTML5/canvas を用いたパーティクルシステムとピクセルベースドレンダリングによるベンチマークページ
    • デモ
    • ソース
    • 解説
  • ② Native-client の準備と雛形
    • 準備
    • 雛形
  • ③ Native-client による①へのC++ぱわー注入
    • デモ
    • ソース
    • 解説

また、今回は時間が少々足りなかった都合(ゴメンナサイゴメンナサイ)、そもそものJavaScriptアプリの最適化(但しこれはメンテナンス性の著しいトレードオフを伴わなければ真の最適化はできないくそ言語なのだけど;w;)や、HTML5/WebGL、Native-client/OpenGL ES 2.0等については年内に余力が取れれば追加記事にしようと思います。

HTML5/canvas を用いたパーティクルシステムとピクセルベースドレンダリングによるベンチマークページ
  • デモ

f:id:USAGI-WRP:20121204015825p:plain

こんな感じの何かとっても重いかもしれないけれどクリスマスっぽいデモが(たぶん)動きます。

画面左上に SCORE と FPS を表示するベンチマークアプリになっています。FPSは凡そ現在のフレームレート、SCOREは一定間隔でFPSを基に加算されデモの終了時に増加が止まる仕組みです。

フルHD(1920x1080)専用です(ぉ
F11など押して全画面でお楽しみ下さいませ。

ちなみに、私の開発環境(記事末尾参照)での Chrome 23 のスコアは 464 でした。

(※動かない場合はこのデモの実行に対してはマシンスペックが足りていないか、canvasやaudioの使えないレガシーなブラウザーを使っているのが原因かもしれません。デバロッパーツールでコンソールを見るとエラーが出ているかもしれません。基本的にはサポートはしませんが悪しからず・x・)

  • ソース
  • 解説

基本的にはコンピューターグラフィックスやシミュレーション分野での基礎技術の1つ、パーティクルシステムをJavaScriptで構築し、表示画面内をピクセル(≠ディスプレイのピクセル)に分割しパーティクルに基づいてピクセルの色付けを行なっています。なお、初期状態の設定ではパーティクルが直接描画される事はありません。

パーティクルは単純なオイラー法によるニュートンの運動方程式により、重力の影響を受けつつ、速度、位置を変化させます。また、パーティクルは色と半径を持っています。

ピクセル(しつこい様ですがディスプレイのピクセルではありません)は画面を所定の分解能で分割した領域で画面をタイル状に埋め尽くしています。そしてピクセルは毎フレーム、運動するパーティクルとの距離から色を更新しています。

ソースコード: https://github.com/usagi/cpp_advent_calendar_2012_4th/blob/gh-pages/HTML5/main.html

HTMLファイルに定義されたHTMLタグはDOCTYPE宣言の他は meta 、 style 、 script のみです。ブラウザーに読み込まれると、

window.addEventListener('load', main);

が定義され、ページロード後に main 関数が呼ばれます。プログラム全体の仕組みとしては、 config が各種設定をまとめ、実行時の一時領域は tmp に整理される様になっています。一部の config はデベロッパーツールのコンソールから値を変える事で即座に適用できます。例えば、

conf.force_gravity = false;

とか評価すると重力が無くなります。また、

conf.present_particles = true;

とか評価するとパーティクルの位置と半径と色が分かる様に描画される様になります。

より詳細についてはソースコードをご覧下さい。

② Native-client の準備と雛形
  • 準備

Native-clientの開発環境導入については、openSUSE-12.2にnative-client開発環境を入れる - C++ ときどき ごはん、わりとてぃーぶれいく☆に書いた様にとても簡単かつLinuxディストリビューターやOS付属のパッケージ管理システムに依存しません。さくっと入れちゃいましょう。環境変数 NACL_SDK_ROOT を通して置く事をお忘れなく。

  • 雛形

Native-clientの開発環境を整えると、個別のバージョンのPepper SDKと同時にその中にexamplesも導入されます。お約束のヘロウワアルドも入っていますので、まずはその辺りをちょいちょい試したり、元にすると良いかもしれません。

しかし、SDK付属のexmapleに入っているプロジェクトではMakefileが冗長な割に低機能だったり、glibcツールチェインを使う際の .nmf 定義は手作業で書く必要があるなど不便です。

と、言う訳で雛形を用意してみました。

https://github.com/usagi/cpp_advent_calendar_2012_4th/tree/gh-pages/native-client_wrp_project_template_kit

機能面では雛形としてまったく足りていない部分や調整途中の部分も多いのですが、ディレクトリー構造やMakefileは役立つかもしれません。特に、このMakefileでは生成する.nexe(Native-clientの配布用の実行ファイルです)を基に依存するライブラリーの .so の定義など面倒な .nmf の作成を完全に自動化しています。

また、JavaScriptとのメッセージング周りなどもmain.jsに実装済みの状態となっていますのでそこら辺もNative-clientを始めてお試ししようかな、そんな時の雛形としては便利かもしれません(main.jsのwrp.nacl.prototype.on_message/post_message)。このmain.jsも238行目のaddEventListener('load',main)によりmain関数が開始する実装になっています。コンソールへのログ出力がデフォルト有効ですので、この雛形は.nexeから標準出力へのログも勿論、JavaScriptレベルでも以下のようなログがブラウザーのJavaScriptコンソールに吐き出されます。テスト、デバッグのお供にどうぞ。

f:id:USAGI-WRP:20121204180220p:plain

このJavaScript側のログ出力は当然負荷が掛かりますので、main.jsの冒頭辺りでの

wrp.etc.log_off = true

とか、それに続くwrp.logオブジェクトの実装を参考に適宜に切り替えて使用して下さい。

このほか、雛形にはx86_64版とi686版のビルドから、ローカルテストサイト作成、ローカルテストサイトをdarkhttpdで立ち上げたり、ブラウザーを立ち上げたりする機能も一応程度ですが付いています。

% make
% make _site

これでNative-client実行ファイルのビルドからローカルテスト用のディレクトリー構造を作成できます。

darkhttpdが使える場合は、

% make && make test-server

とかするとテストサーバーを起動します。ログ流し、分離の都合、フォアグラウンドで通常動作させます。クライアントでのテストサーバーへの接続は、

% make test-client

とか別端末を開いてから実行する設計です。これは、ChromiumブラウザーがNative-clientの標準出力及び標準エラー出力Linuxであれば対応してくれる(Windowsではcmd.exeからChromiumを起動しても何も出ませんはずです)為、そのログ取りに便利という事で別端末を前提とした仕様にしてあります。

実際、ログ取りのサンプルとして、 include/wrp/log.hxx なるちょこちょこと以前書いてあったログ取りクラスをこの雛形のプロジェクトでは利用する様になっていますので、すぐに標準出力等の動作を確認できる状態になっています。

ソースコード以外の生成物、.nexe、.nmf、_siteなどを削除したい場合は、

% make clean

とします。

さて、Native-clientの動作する仕組みについては、

HTMLがロードされる→HTMLのembedにより対応ブラウザー(Chromium)であればNative-clientが起動し

→.nmfを読み込みNative-client実行ファイル.nexeがロードされ→.nmfより必要なら.soもロードし→pp::CreateModuleが実行されpp::Module(から派生した自分のアプリのクラス)が作られ→pp::Module::CreateInstanceが実行されpp::Instance(から派生した自分のアプリのクラス)が作成され→インスタンスが動作する

という流れです。動作中のpp::Instanceは必要ならばHTMLページのJavaScriptとの相互のメッセージングを備えていますので、これだけでも割とアレコレできます。他にAudioやOpenGL ES 2.0も扱うことができますが、それはまた別の機会に。

動作の流れは実際のローカルテストサイトまでビルドしてから、その内容物で追いかけた方が良いでしょう。(.nmfは.nexeから生成する為、雛形のソースコード中には含まれていないなどの都合もありますし。)

なお、雛形ではアプリ名を app_name で作成してありますので、そこら辺は使う際に適宜に書き換えて下さい。また、 bland_name もソースコード中で名前空間の定義に使っていますので、こちらも同様に書き換えて使用して下さい。書き換えはfind|sedとfind|tr|mvに慣れて居ればそれでも良いのですが、こんな時はmsrpを使うと楽ですよ。

% msrp app_name cppac2012_4th .
(  changed) ./main.js
(  changed) ./Makefile
(  changed) ./main.html
(  changed) ./src.cxx/common.hxx
(renaming f) ./src.cxx/app_name-client-nacl.cxx => ./src.cxx/cppac2012_4th-client-nacl.cxx
trying: plain rename
% msrp bland_name WonderRabbitProject .          
(  changed) ./src.cxx/common.hxx

こんな具合でファイルの中身の文字列、ファイル名を一気に置換できます。正規表現にも対応しているのでmsrpを使えると何かと便利です。

③ Native-client による①へのC++ぱわー注入
  • デモ

(※Chromium 14以上でNative-clientのデモが動作しない場合、お使いのChromechrome://flags/ より、(野良)Native-clientの実行を許可する必要があるかもしれません。)

  • ソース
  • 解説

私の環境ではスコアが874になりました^-^

先のHTML5(pure)版に比べると凡そ1.88倍もなめらかに動くので、見ていてもかろうじて映像系のデモとして見れる…かなぁ…うーむ^^;

こちらは、パーティクルシステムはNative-client側に実装し、

  1. JavaScript → Native-Client : 次の pixels を計算しておくんなまし!
  2. Native-Client : パーティクルシステムを dt [sec] だけ進め pixels を決定
  3. Native-Client → JavaScript : できたよん っ[☆☆☆…(ピクセルの色情報群)]
  4. JavaScript : pixels を canvas に描画するデス!

この 1-2-3-4 をJavaScript側のメインループで回しています。

ちなみに、JavaScriptでメインループを回す際は、ビジーループは論外として、setInterval/setTimeoutを使います。どちらが良いかは負荷次第で、実は先に実装したHTML5(pure)版でも conf.main_loop_timeout を true/false で setInterval/setTimeout を切り替えて実行する様に仕込んであります。

通常は setInterval を用いた方がループ処理の為の負荷が軽くなるようです。しかし、 setInterval で指定する呼び出し間隔に対して過負荷な処理については、 setTimeout(arguments.callee, 1) などして回した方がどんなに重かろうとも一応はコールスタック的にも健全な状態を維持出来る為か setInterval で回すよりもループ処理による負荷は軽く済むようです。(この判断はChromium 24でループを回して行った定性的な評価によります。)

今回は Native-client のコード内で pixels の確定まで行なってしまい、上記工程の 3 にて結果を JavaScript へメッセージングしましたが、実際に実用的な面白いアプリとなると、JavaScriptから受け取る値に応じてNative-clientからの応答(出力)を変化させるなどしたい場合もあるかもしれません。しかし、今のところ JavaScript → Native-client のメッセージングには未実装機能があり、

[6:6:1204/185322:ERROR:message_channel.cc(129)] Not implemented reached in PP_Var webkit::ppapi::<unnamed>::CopyPPVar(const PP_Var&)

なんてエラーを吐いたりする事があります^^; JavaScript側からNative-clientに対してpostMessageする際には、スカラーっぽい値または文字列しか仕様としても実装されておらず、ハッシュオブジェクト { ... } や内部的は割と等価な配列オブジェクト [ ... ] をNative-client側へポストすると発生してしまいます。このため、こうしたデータは JSON.stringify でJSON化、つまり文字列にシリアライズしてNative-clientへポストするなどの手間が必要となります。

逆に、今回の様に、Native-client → JavaScript のメッセージングでは <ppapi/var_array_buffer>で定義されている pp::VarArrayBuffer を送り付けられます。今回は pixels = [pixel,pixel,pixel,...] の様なイメージの配列オブジェクトをNative-clientからJavaScriptへ送り付けて居ます。

とりあえず、Native-client → JavaScript のメッセージングは JSON でピクセル群のカラーデータを送り付ける実装になっています。

Conclusion

このヘタレというかダメダメな実装状態でもNative-clientのJavaScriptに対する優位性が十分に伝わった(ことにしてください・x・)かと思います。また、今回はNative-clientとJavaScriptのメッセージングを(無理矢理)使い、協調的に動作させる事にも可能性を見いだせた方もいらっしゃったかもしれません。

また、少々面倒なglibcツールチェイン時のライブラリーの同梱や.nmfの準備なども今回配布するお手軽Makefile付きのNative-clientプロジェクト雛形を使って頂ければほぼ苦労する事も無いと思います。まだほんのさわり程度しかNative-clientのぱわーを使えて居ませんし、そもそも今回のデモの動作方式では潜在能力も活かせる状態ではありませんでしたが、そんな中でも何かしらお伝えできたり、クリスマスへのアドベンカレンダーとして、或いはベンチマークアプリとして少しでも楽しんで頂けたなら幸いです。

引き続き、ブログではNative-clientも扱い続けたいと思います。

・w・;

実はNative-clientぱわーどの方のソースはとてもじゃないけど見せられないよ>< な状態です。徹夜納期でなんとか動く状態でデプロイしたに過ぎないとんでもない状態です。余裕を見てどうにかしようかなーと思います。

また、今回は中途半端にHTML5/JavaScriptとメッセージング連携していますが、そもそもNative-clientにも強力な描画APIがありますから、そちらのガチなNaClコードも時間を取れ次第載せたいなーと思います。

=w=;

Native-client について一言だけグサリと言いたいとすれば、いーかげんに GCC-4.4 ベースとか卒業してーーーーー>< って事くらいかな!

そうそう…Eigen使おうかと思ったんだけど、どうもコンパイルした.nexeが動作テストしてみたら例外落ちするっぽい状態で、結局使うのを止めて簡単なvector2だとかcolorだとかさくっと定義して使ってたり。そのへんも後できちんと調査したいなー。

ちなみに、今回の企画、実はこの企画の前にWebRTCとカメラ映像処理も企んだのだけど、どうもLinuxChromiumはWebRTCのカメラ機能が未実装らしくてね(´・ω・`) libusb動かないかなーとか画策もしたものの、素直に諦めるなどしていたのでした。WebRTCがLinux版でも実装されたら遊ぼうかと思ってるよ。

Future

時間が取れ次第、年末にかけて逐次もうちょっとこのネタを引きずろうと思っています。

  • 落ち着いてソースを整理したい、特にC++のコードがC++になってない恥ずかしー状態過ぎるのがナントモ(´・ω・`)
  • 実装の効率化
  • Multi-threading, etc.
  • HTML5/WebGL
  • Native-client/Graphics2D
  • 3D化

Environments

執筆時に用いた環境の諸元を掲載します。

Hardwares
Softwares

openSUSE-12.2にdarkhttpdをcheckinstallしてrpmで入れる

コマンドからさくっと使える割に一時的な物置きのみならずウェブ開発用にも重宝する簡易HTTPdのdarkhttpdを導入しましょう。

と、思ったけれど、

% zypper se darkhttpd
 ...

やはり出て来なかった。ウェブのパッケージ検索でも出てこない

darkhttpdは普通にソースをtar玉で公開しているし、ビルドも数秒と掛からないコンパクトな良い子。`make`して`cp darkhttpd ~/opt/bin`とかしても構わないのだけど、checkinstallでお手軽にrpmにして導入してみる。

先ずはcheckinstallを導入しましょう。こちらはzypperで導入できます。

% zypper in checkinstall-lang
 ...

langの依存パッケージで本体も入るのでコマンド打つのが楽なんですゴメンナサイゴメンナサイ・w・

さて、checkinstallを導入できたなら早速使ってみましょう。なお、zypperで導入したcheckinstallは`/usr/sbin`に導入されています。よってrootじゃないと使えません。

% sudo su
 ...
% which checkinstall
/usr/sbin/checkinstall

checkinstallを今回はdarkhttpdのtar玉を展開したディレクトリーで実行します。

% ls
AUTHORS  Makefile  README  darkhttpd.c
% checkinstall
 ...

対話を進めます。

checkinstall 1.6.2, Copyright 2002 Felipe Eduardo Sanchez Diaz Duran
           このソフトウェアはGNU GPLの下でリリースしています。


The package documentation directory ./doc-pak does not exist. 
Should I create a default set of package docs?  [y]: 

パッケージのドキュメンテーションを準備..OK

使用するパッケージ方式を選んでください。
Slackwareなら[S], RPMなら[R], Debianなら[D]を入力R


このパッケージの説明を書いてください
説明の末尾は空行かEOFにしてください。
>> When you need a web server in a hurry.
>> 

**************************************
**** RPM package creation selected ***
**************************************

このパッケージは以下の内容で構成されます: 

1 -  Summary: [ When you need a web server in a hurry. ]
2 -  Name:    [ darkhttpd ]
3 -  Version: [ 1.8 ]
4 -  Release: [ 1 ]
5 -  License: [ GPL ]
6 -  Group:   [ Applications/System ]
7 -  Architecture: [ x86_64 ]
8 -  Source location: [ darkhttpd-1.8 ]
9 -  Alternate source location: [  ]
10 - Requires: [  ]
11 - Provides: [ darkhttpd ]

変更するものの番号を入力してください。Enterで続行します: 

Installing with make...Installing with install...

========================= インストールの結果 ===========================
make: *** ターゲット `install' を make するルールがありません.  中止.

****  インストールは失敗しました。パッケージの作成を中断します

クリーンアップ..OK

Bye.

oops!!

darkhttpdのMakefileにはinstallルールが無いのでした。

% cat Makefile
CC?=cc
CFLAGS?=-O
LIBS=`[ \`uname\` = "SunOS" ] && echo -lsocket -lnsl`

.PHONY: all clean

all: darkhttpd

darkhttpd: darkhttpd.c
        $(CC) $(CFLAGS) $(LIBS) darkhttpd.c -o $@

clean:
        rm -f darkhttpd core darkhttpd.core

installルールを追記しましょう・w・;

% echo -e '\ninstall: darkhttpd\n\tinstall darkhttpd /usr/local/bin\n' >> Makefile

さてさてワンモア・x・

% checkinstall

checkinstall 1.6.2, Copyright 2002 Felipe Eduardo Sanchez Diaz Duran
           このソフトウェアはGNU GPLの下でリリースしています。



使用するパッケージ方式を選んでください。
Slackwareなら[S], RPMなら[R], Debianなら[D]を入力R


**************************************
**** RPM package creation selected ***
**************************************

このパッケージは以下の内容で構成されます: 

1 -  Summary: [ When you need a web server in a hurry. ]
2 -  Name:    [ darkhttpd ]
3 -  Version: [ 1.8 ]
4 -  Release: [ 1 ]
5 -  License: [ GPL ]
6 -  Group:   [ Applications/System ]
7 -  Architecture: [ x86_64 ]
8 -  Source location: [ darkhttpd-1.8 ]
9 -  Alternate source location: [  ]
10 - Requires: [  ]
11 - Provides: [ darkhttpd ]

変更するものの番号を入力してください。Enterで続行します: 

Installing with make...Installing with install...

========================= インストールの結果 ===========================
cc -O `[ \`uname\` = "SunOS" ] && echo -lsocket -lnsl` darkhttpd.c -o darkhttpd
install darkhttpd /usr/local/bin

======================== インストールに成功しました ==========================

Copying documentation directory...
./
./AUTHORS
./README

tempディレクトリにファイルをコピー..OK

Stripping ELF binaries and libraries...OK

manページを圧縮..OK

ファイルリストを作成..OK

RPMパッケージを作成..OK

注意: パッケージはインストールされません

tempファイルを削除..OK

バックアップパッケージを書き込み..OK
OK

temp dirを削除..OK


**********************************************************************

 Done. The new package has been saved to

 /usr/src/packages/RPMS/x86_64/darkhttpd-1.8-1.x86_64.rpm
 You can install it in your system anytime using: 

      rpm -i darkhttpd-1.8-1.x86_64.rpm

**********************************************************************

先ほど失敗した際に定義済みだった内容が残っているので Enter 1回で rpm 作成まで進みました。最後に出ているメッセージを基に、パッケージを`rpm -i`でインストールしましょう。

% rpm -i /usr/src/packages/RPMS/x86_64/darkhttpd-1.8-1.x86_64.rpm

rootから抜けて一般ユーザーで`darkhttpd`を使ってみましょう。

% exit
% which darkhttpd
/usr/local/bin/darkhttpd
% darkhttpd . --daemon && curl http://localhost:8080 && pkill darkhttpd
darkhttpd/1.8, copyright (c) 2003-2011 Emil Mikulic.
listening on: http://0.0.0.0:8080/
<html>
<head>
 <title>/</title>
</head>
<body>
<h1>/</h1>
<tt><pre>
<a href="..">..</a>/
<a href="AUTHORS">AUTHORS</a>                                          56
<a href="Makefile">Makefile</a>                                        274
<a href="README">README</a>                                         1352
<a href="backup-120220121215-pre-darkhttpd-1.8.tgz">backup-120220121215-pre-darkhttpd-1.8.tgz</a>   5201601
<a href="darkhttpd">darkhttpd</a>                                     47038
<a href="darkhttpd.c">darkhttpd.c</a>                                   74774
<a href="description-pak">description-pak</a>                                  39
<a href="doc-pak">doc-pak</a>/
</pre></tt>
<hr>
Generated by darkhttpd/1.8 on Sun, 02 Dec 2012 03:28:58 GMT
</body>
</html>

動作テストの簡単の為`curl`でHTTP GETしていますが、もちろんChrome等のウェブブラウザーでも確認できます。なお、darkhttpdの使い方についてはオプション無しで`darkhttpd`を実行すれば表示されます。