メモ: coffeescriptつこうてみた
今作ってるモノで試しにCoffeeScript使ってみたら存外に書きやすく、かつ楽しかったのでメモ。
本家
CoffeeScriptはRuby,Python,Haskellから影響を受けたプログラミング言語です。
CoffeeScriptの文法にしたがって書いたものをcoffeeコマンドでコンパイルするとJavaScriptになります。
要するに、JavaScriptのシンタックスが嫌いとか、JavaScriptゴチャゴチャしがちでなんとかしたいとか、Webブラウザで実行するスクリプトもRubyとかPythonとかHaskellとかみたいに書きたいよーとか思ってる人にはおすすめです。
長所と短所
まだ使い始めて1週間くらいなのですが、現時点で感じることは
長所
- 同じ事をするにもJavaScriptよりコード量が少なくてすむ(Wikipediaによると1/3)
- 吐き出すのがJavaScriptなので、うまく動かない際にも、何が起こってるのか理解しやすい
- 書いてて気持ちいい
- 読みやすい
短所
- ブラウザのエラー出力は、コンパイル結果のJavaScript上の位置を示すので、そこからCoffeeScript上の間違いを自分で探す必要あり
- 一部、IenternetExplorer8では実行できないJavaScriptを吐き出す
インストール
インストールにはNode.jsが必要です。Ubuntuなら
$ sudo apt-get install nodejs
です。Macとかでも、多分homebrewとかportsとかにあると思います。たぶん。
Node.jsが入ったら、あとはnpmコマンドでインストールですが、システム全体で共用するなら
$ sudo npm install -g coffee-script
あるいは、なにかしらのプロジェクトを作ってそのプロジェクトディレクトリ内にインストールしたいなら
$ mkdir hoge_project $ cd hoge_project $ npm install coffee-script
てやります。
Hello World
とりあえず、最初のCoffeeScriptを作ってみます。
emacsなど好みのエディタで"helloworld.coffee"というファイルを新規作成し、以下のようにかきます。
alert "hello world"
コンパイルしてみます。
$ coffee -c helloworld.coffee
helloworld.jsというファイルができたと思います。中身を見てみます。
// Generated by CoffeeScript 1.4.0 (function() { alert("hello world"); }).call(this);
このJavaScriptをブラウザから呼ぶためにHTMLファイルを用意します。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>はじめてのCoffeeScript</title> <script src="helloworld.js"></script> </head> <body> </body> </html>
Ajaxとかするわけじゃないので、このままHTMLファイルダブルクリックで開けば実行できます。
ある程度大きなプロジェクトではまとまった機能毎にクラスにまとめるとかするといいと思います。
class ClassSample # インスタンス変数 name: null # 他の関数の呼び出しは "@間数名()" で start_sample: () -> @hello() @name = "shin" @my_name() @map_sample() # Rubyみたいに、引数にデフォルト値を指定できます # Rubyみたいに、文字列に#{}で値を埋め込めます hello: (str = "world")-> alert "hello #{str}" # インスタンス変数の参照は "@変数名" で my_name: () -> alert "myname is #{@name}" # 配列内の各要素に処理を施した新たな配列をかえします # 無名関数は "(引数) -> 処理" です map_sample: () -> array = [1,2,3,4,5,6] new_array = array.map (e) -> e * e console.log new_array
Rubyユーザが気をつけるべきことは、CoffeeScriptではブロックの終わりのendがいらないってことです。
この辺はPythonの影響があるらしく、インデントでブロックレベルを表現します。
たとえば条件分岐などは
a = 10 if a > 11 alert "10より大きい" else alert "10以下"
てな具合です。classや関数、ループなども同様となります。
コンパイルするとこうなります
$ coffee -c class_sample.coffee
// Generated by CoffeeScript 1.4.0 (function() { var ClassSample; ClassSample = (function() { function ClassSample() {} ClassSample.prototype.name = null; ClassSample.prototype.start_sample = function() { this.hello(); this.name = "shin"; this.my_name(); return this.map_sample(); }; ClassSample.prototype.hello = function(str) { if (str == null) { str = "world"; } return alert("hello " + str); }; ClassSample.prototype.my_name = function() { return alert("myname is " + this.name); }; ClassSample.prototype.map_sample = function() { var array, new_array; array = [1, 2, 3, 4, 5, 6]; new_array = array.map(function(e) { return e * e; }); return console.log(new_array); }; return ClassSample; })(); }).call(this);
ただ、これだと外部から呼び出せません。
外部から呼び出せるようにするには、コンパイルオプションに-b(--bare)を付加します。
$ coffee -bc class_sample.coffee
できあがったものはこうなります。
// Generated by CoffeeScript 1.4.0 var ClassSample; ClassSample = (function() { function ClassSample() {} ClassSample.prototype.name = null; ClassSample.prototype.start_sample = function() { this.hello(); this.name = "shin"; this.my_name(); return this.map_sample(); }; ClassSample.prototype.hello = function(str) { if (str == null) { str = "world"; } return alert("hello " + str); }; ClassSample.prototype.my_name = function() { return alert("myname is " + this.name); }; ClassSample.prototype.map_sample = function() { var array, new_array; array = [1, 2, 3, 4, 5, 6]; new_array = array.map(function(e) { return e * e; }); return console.log(new_array); }; return ClassSample; })();
これをよびだすHTMLファイルを用意します。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>はじめてのCoffeeScript</title> <script src="class_sample.js"></script> <script> var sample = new ClassSample(); sample.start_sample(); </script> </head> <body> </body> </html>
自動コンパイルとか
ここで、ファイルを編集する度にコンパイルするの面倒くさいですよね?
coffeeコマンドにはファイルを監視してて、変更されたらそのファイルを自動的にコンパイルしてくれるオプションがあります。
"-w"をつければ自動コンパイルです。
$ coffee -wc class_sample.coffee
さっきの-bと合わせると
$ coffee -wbc class_sample.coffee
また、ソースファイルの場所とJavaScriptの吐き出し先を指定するとこんな感じです。
$ coffee -wbc -o js/build src/coffee/*.coffee
CoffeeScriptにはcakeというコマンドがあってCakefileを作っておくとタスクが自動化できます。
sys = require "sys" fs = require "fs" exec = require("child_process").exec util = require "util" COFFEE = "coffee" OPTION = "-cb" WATCH_OPTION = "-wbc" SRCDIR = "./" OUTDIR = "js/build" # # @desc 与えられたオプションでコンパイルします # compile = (option) -> util.log "compiling coffee..." command = "#{COFFEE} #{option} -o #{OUTDIR} #{SRCDIR}/*.coffee" exec command, (error, stdout, stderror) -> util.log error if error util.log stdout if stdout util.log stderror if stderror if error util.log "failure" else util.log("ok") task "build", "compile coffee to java script", -> compile(OPTION) task "watch", "compile coffee to java script", -> compile(WATCH_OPTION)
上記だと、"cake build"とかやるとコンパイルできます。
ただ、これ、コンパイルエラーが出力されないんですよね... たぶん僕が何か間違ってるんでしょうが。。
まあこれくらいの内容なら普通にMakefileでも書けます。
COFFEE=coffee src_dir=. out_dir=js/build all: coffee coffee: @$(COFFEE) -bc -o ${out_dir} ${src_dir}/*.coffee coffee-auto: @$(COFFEE) -wbc -o ${out_dir} ${src_dir}/*.coffee clean: rm js/build/*.js
ビルドするコマンドが make coffee なんてちょっとオシャレですね(ぉ
今回はここまで。
Ruby + dino で、RubyからArduinoを制御してみた
昨年末にArduino買ってちょくちょく遊んでます。オープンソースの基板で、デジタル入力14点(うち6点は出力として使用可)、アナログ入力6点を持ち、3000円未満と遊ぶにはちょうどいい金額と機能。
ただ、これCによく似たArduino言語でコード書くんですよね。当然、軟弱者の私としましては慣れ親しんだRubyでコードかけないかなって思って調べてみました。
まっさきに調べたのは組み込み向けのRuby、mrubyでいけないかってことでしたが手元にあるArduino Unoではリソース的に厳しそうです(てか実際やってみましたが、リソース以前にコンパイル通らず。
ちなみにUnoよりリソースに余裕のあるArduino DueとChipKit32ではmrubyが動いてるらしく、mrubyのリポジトリにそれ向けのビルドセッティングが入ってたりします。
https://github.com/mruby/mruby/tree/master/examples/targets
で、じゃあArduinoは入出力として使って、BeagleboardとかRaspberryPiとかと組み合わせ使うのはどうなんだろうってまた調べてたらdinoというありがたいgemがありました。
のでそれ使ってみます。
Arduinoの用意
Arduinoの基板はスイッチサイエンスでLEDやジャンパと一緒に4000円ほどで売ってます。
もっとも標準的なUnoでデジタルが14点、アナログが6点あります。
ArduinoのIDEがまだ落としてない場合はこれを落としておきます。
Arduino-Software
Arduinoへスケッチをアップロード
dinoのリポジトリにあるdu.inoをダウンロードして、適当なところに保存。Arduino IDEで開きます。
Arduino IDEでdu.inoをコンパイル・基板にアップロードします。
以上でArduino側の用意は終わりです。ここまでやったらArduino IDEは終了してかまいません。
dinoライブラリの準備
dinoはgemにありますので、これをインストールします。
$ sudo gem install dino
とりあえず、ためしにLEDのチカチカやってみます。
これまた親切丁寧に配線図?があるのでこのとおりにLEDをさします。
試しにirbから制御してみます。
require "dino" => true board = Dino::Board.new(Dino::TxRx.new) => #<Dino::Board:0x..... led = Dino::Components::Led.new(pin: 13, board: board) => #<Dino::Components::Led:0x.....
ここまでで13番のデジタル入出力を制御する準備ができました。
では13番にデジタル出力してLEDを光らせてみます。
led.send :on
では消してみます。
led.send :off
とりあえず今回はここまでですが、他にもいろいろとできそうなので、調べてみます。できたらZigBeeと組み合わせてセンサーネットワークとか作ってみたい。
Phabricatorセットアップした時のメモ
継続的に使うかどうかはこれからですが、コードレビューツールのPhabricatorをセットアップしてみました。
なんでもFacebook内部で使われていたものがオープンソースになったものだとか。
もっともPhabricatorはコードレビューだけじゃなくてバグトラックやレポジトリブラウザなど色々多機能なようです。
goo Wikipediaより
Released in early 2011, Phabricator is an open source collection of web applications originally developed at Facebook where it is used there by the engineering team. It includes applications for common, collaborative software engineering tasks, such as code review, repository viewing, bug and issue tracking, a pastebin, and other applications geared toward collaboration of software engineers. It is humorously themed, aiming to make tasks like code review be more enjoyable.
本家サイトはこちら
http://phabricator.org/
セットアップ
セットアップの仕方はこちらにあります。
Installation Guide
OSはLinux的なOSなら良し、みたいなことが書いてあります。MacOSXもいけるようですね。試してませんが。
私はXen上のDebian GNU/Linux squeeze で行いました。
まずは依存関係から以下が必要。
あとインストール時にgitが必要でした。
Debianなので全てaptで入れました。
$ sudo aptitude install mysql-server mysql-client $ sudo aptitude install libapache2-mod-php5 php5-curl php-apc php5-mysql
それと、ユーザ登録したときなどにメールを送信する必要があり、PHPから直接SMTPサーバに
送信する方法と、ローカルのsendmailコマンドを使う方法があるのですが、私は後者を
設定しました。というかPHPから直接送るのはうまくいきませんでした。私がPHPをよく知らないせいだと思いますが。
なのでsendmailコマンドを入れるためにpostfixをインストール
$ sudo aptitude install postfix
MySQLのセットアップ
といってもPhabricator用のユーザを作るだけですが。
$ mysql -uroot -p Enter password:(パスワード入力) GRANT ALL PRIVILEGES ON *.* TO phab@localhost IDENTIFIED BY 'pass' WITH GRANT OPTION; GRANT ALL PRIVILEGES ON *.* TO phab@"%" IDENTIFIED BY 'pass' WITH GRANT OPTION;
Phabricatorのインストール
私は"/usr/local/phabricator"というディレクトリを切ってそこで作業をしました。
$ sudo mkdir /usr/local/phabricator $ sudo chown myuser:myuser /usr/local/phabricator $ cd /usr/local/phabricator $ git clone git://github.com/facebook/libphutil.git $ git clone git://github.com/facebook/arcanist.git $ git clone git://github.com/facebook/phabricator.git $ cd phabricator $ git submodule update --init
設定
設定関係はConfiguration Guideに
設定ファイルを作ります。
$ cd /usr/local/phabricator/phabricator/conf $ mkdir custom $ vi custom/myconfig.conf.php
conf/custom/myconfig.conf.php の内容はこんな感じです。
<?php return array( // Important! This will put Phabricator into setup mode to help you // configure things. 'phabricator.setup' => true, // This will be the base domain for your install, and must be configured. // Use "https://" if you have SSL. See below for some notes. 'phabricator.base-uri' => 'http://192.168.0.100/', // Connection information for MySQL. 'mysql.host' => 'localhost', 'mysql.user' => 'phab', 'mysql.pass' => 'pass', // Basic email domain configuration. 'metamta.default-address' => 'review@mydomain.co.jp', 'metamta.domain' => 'mydomain.co.jp', 'metamta.mail-adapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter' // NOTE: Check default.conf.php for detailed explanations of all the // configuration options, including these. ) + phabricator_read_config_file('production'); ?>
ここでは稼働ホストがローカルネットワークの192.168.0.100と仮定しています。
このファイルを設定ファイルとして読み込むように~/.bashrcに保存しておきます。
$ vi ~/.bashrc export PHABRICATOR_ENV=custom/myconfig
保存して終了。
そしてデータベースの初期化。
$ cd /usr/local/phabricator/phabricator/ $ ./bin/storage upgrade
Apacheの設定
このホストでは他に用途もないのでApacheはPhabricator専用にしました。
$ sudo vi /etc/apache2/sites-enabled/000-default
こんなかんじで
<VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /usr/local/phabricator/phabricator/webroot RewriteEngine on RewriteRule ^/rsrc/(.*) - [L,QSA] RewriteRule ^/favicon.ico - [L,QSA] RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA] SetEnv PHABRICATOR_ENV custom/myconfig ......
あとはrewriteが使えるようにしておきます。
$ sudo a2enmod rewrite
ここまでやったらApache再起動。
$ sudo /etc/init.d/apache2 restart
で、この場合は http://192.168.0.100 にブラウザでアクセスすると各種設定が実行されるはず。
多分途中でFailureが出ていろいろ言われるのでその都度修正をかけます。
いろいろ修正をかけて、完了のメッセージが出たら先ほどの設定ファイルから"'phabricator.setup' => true"の行をコメントアウトします。
conf/custom/myconfig.conf.php
<?php return array( // Important! This will put Phabricator into setup mode to help you // configure things. //'phabricator.setup' => true, <=コメントアウト ....
それから最初のユーザを作ります。2人目からはWeb画面上で作れます。
$ cd /usr/local/phabricator/phabricator/ $ ./bin/accountadmin
そして再び http://192.168.0.100 にアクセスするとトップ画面が表示されるはず。
Sphinx + blockdiag + seqdiag + nwdiag + actdiag でドキュメント書いて日本語PDFへ出力したメモ
Sphinxのインストール
Macであればhomebrewでインストールできますが、残念ながらそのままではPDF形式で書き出す時に日本語が使えません。既にインストール済みの場合はパッチを当てる事もできますが、ここはパッチ済みのものをインストールします。
なお、ここで使っている"easy_install"コマンドはPythonのパッケージ管理スクリプトです。MacOSXであればはじめからPythonが入っていますので、このコマンドは特に何もしなくても使えるはずです。
$ easy_install https://bitbucket.org/sphinxjp/website/downloads/Sphinx-1.1.2sphinxjp-latex.tar.gz
Latexのインストール
Ubuntu Linux
aptで入れられます。なんか色々インストールしてようやく日本語PDF出力できるようになりました。
無駄な事もしてるかもしれませんが、とりあえずやったことまるっと書いておきます。
$ sudo apt-get install texlive texlive-base texlive-latex3 \ platex-bin okumura-clsfiles ptex-base \ ptex-bin dvipsk-ja xdvik-ja ptex-jisfonts \ gs-cjk-resource jmpost dvipdfmx \ texlive-math-extra okumura-clsfiles \ jbibtex-bin mendexk $ sudo apt-get install texlive-latex-extra nkf dvipng $ sudo apt-add-repository ppa:texlive-backports/ppa $ sudo apt-get update $ sudo apt-get install texlive-lang-cjk $ sudo apt-get install xdvik-ja $ sudo apt-get install texlive-latex-extra $ sudo apt-get install texlive-fonts-recommended $ sudo apt-get install ttf-sazanami-gothic ttf-sazanami-mincho
MacOSX
Sphinxをインストールすれば、既にHTML等への書き出しはできますが、PDFに書き出すにはLatexが必要です。
MacではTexLive( http://www.tug.org/mactex/2011/ )をインストールすればLatexと各種付属コマンドが使えるようになります。
また、MacでのTexliveのインストール方法や各種設定は
Mac-Tex Wiki 奥村研究所
が詳しいです。
ここではTexLive-2011のトップページから最新のMacTexをダウンロードしてインストールします。
ダウンロードページ http://www.tug.org/mactex/2011/
blockdiagのインストール
blockdiagはブロック図生成ツールでSphinxと連携する事ができます。
blockdiagのインストールはeasy_installから行えます。
# easy_install blockdiag
更にSphinxのblockdiag拡張をインストールします。
# easy_install sphinxcontrib-blockdiag
seqdiagのインストール
seqdiagはシーケンス図生成ツールでSphinxと連携する事ができます。
seqdiagのインストールはeasy_installから行えます。
# easy_install seqdiag
更にSphinxのseqdiag拡張をインストールします。
# easy_install sphinxcontrib-seqdiag
nwdiagのインストール
nwdiagはネットワーク図生成ツールでSphinxと連携する事ができます。
nwdiagのインストールはeasy_installから行えます。
# easy_install nwdiag
更にSphinxのnwdiag拡張をインストールします。
# easy_install sphinxcontrib-nwdiag
また、シスコルータのアイコンを使用する為の拡張をインストールします
# easy_install blockdiagcontrib-cisco
利用出来るアイコンは `シスコアイコン一覧 を参照してください。
- 追記
私のMacOSX環境ではシスコルータを使おうとすると"decoder jpeg not available"というエラーがでて使えませんでした。回避するには一旦PILを削除して入れ直します。
# brew install libjpeg (既に存在するというメッセージが出る事を確認) # mv /Library/Python/2.7/site-packages/PIL-1.1.7-py2.7-macosx-10.7-intel.egg /tmp/ # easy_install pil
actdiagのインストール
nwdiagはネットワーク図生成ツールでSphinxと連携する事ができます。
nwdiagのインストールはeasy_installから行えます。
# easy_install actdiag
更にSphinxのactdiag拡張をインストールします。
# easy_install sphinxcontrib-actdiag
以上でセットアップ完了。
sphinx-quickstartによるプロジェクト生成
プロジェクトの生成はコマンドで行います。
$ sphinx-quickstart
いくつか質問されます。絶対に回答しなければならないのは、次の項目です。後はEnterキーだけで大丈夫です。
- プロジェクト名
- バージョン番号
- 著者の名前
プロジェクトの初期設定
日本語PDFを書き出し、さらにblockdiag等によるダイアグラム拡張を有効にするにはプロジェクト毎に以下の設定を行う必要があります。
プロジェクトディレクトリの直下にあるconf.pyを編集します。
# 言語の設定(既にあるので変更) language = 'ja' # LaTeX の docclass 設定(新規で追加) latex_docclass = {'manual': 'jsbook'} # Enabled extensions(既にあるのでblockdiag,seqdiag,nwdiag拡張を追加) extensions = ['sphinxcontrib.blockdiag', 'sphinxcontrib.seqdiag', 'sphinxcontrib.nwdiag', 'sphinxcontrib.actdiag'] # Fontpath for blockdiag (truetype font) (新規追加する) # Mac OSXでOsakaフォントを使う場合 blockdiag_fontpath = '/Library/Fonts/Osaka.ttf' # Ubuntu Linuxでsazanami-minhchoを使う場合 blockdiag_fontpath = '/usr/share/fonts/truetype/sazanami/sazanami-mincho.ttf' # 以下共通 blockdiag_antialias = True seqdiag_fontpath = blockdiag_fontpath seqdiag_antialias = True nwdiag_fontpath = blockdiag_fontpath nwdiag_antialias = True actdiag_fontpath = blockdiag_fontpath actdiag_antialias = True
Ubuntu 12.04 on beagleboard-xm Rev.C でErlang動いた
Ubuntu 12.04インストール
正月にAndroid動かしてからほったらしになってたbeagleboard-xmですが、ふと思い立ってubuntu入れてみました。
beagleboard-xmへのubuntuのインストールは至って簡単。
ここを参考にさせていただいたらなにもつまづかずにできました。
$ wget http://cdimage.ubuntu.com/releases/12.04/release/ubuntu-12.04-preinstalled-desktop-armhf+omap.img.gz $ gunzip ubuntu-12.04-preinstalled-desktop-armhf+omap.img.gz
SDカードを挿入してsyslogでデバイスを確認。sdcでした.
ddコマンドでイメージファイルをSDカードにコピー。
$ sudo dd bs=4M if=ubuntu-12.04-preinstalled-desktop-armhf+omap.img of=/dev/sdc
以上w あとはこのSDカードをbeagleboard-xmに差して起動。初回はシステム設定ですが、起動までに結構時間がかかります。
さすがにunityだと重くてもっさり加減が我慢できなかったので、以前開発マシンで使っていたWindowMakerを入れることにしました。
$ sudo apt-get install wmaker wmaker-data menu
一旦ログアウトして、ログイン時にWindowMakerを選択すればWindowMakerが起動します。
で、上の写真みたいな感じに。
unityではものすごくモッサリしてましたが、WindowMakerに変えたらサクサク動きます。
あと全体的にSDカードの読み書きがボトルネックになっている感は否めません。最初立ち上がりが遅いと思ってましたが、原因はCPUよりSDカードからの読み込みにありそうです。
なのでSDカードはケチらずに速いやつを買うことをお勧めします。
Erlang RB15B02のインストール
このままじゃなんなんで、Erlang入れてみました。Erlangてサーバーのイメージ強いですけど、てか実際その方面での使用がほとんどでしょうけど、実は組み込みに有利な特性ももってるようです。
いわゆるソフトリアルタイム性ですが、Erlangは各プロセスごとにGCを持ってるそうなので、GCが走ったために全体がグッと止まるということがないようです。試してませんがw
あと、そのプロセスごとのGCはプロセスが破棄されるときに、そのプロセスが確保していた領域を開放するそうなので、メモリをたくさん使う処理は、それ用にプロセス作ってそこで処理をして、そのプロセスを破棄すればGCなのに任意のタイミングでのメモリ解放ができてかつ、それが他のプロセスに、ソフトリアルタイムのレベルでは影響しないとかなんとか。
で、入れてみるわけですが、普通に入りました。
aptであったのですが、なんかそれだと面白くないのでソースから入れてみました。
以前落としてたotp_src_R15B02をbeagleboard上にscpして
$ tar xvzf otp_src_R15B02.tar.gz $ cd otp_src_R15B02/ $ sudo apt-get install libncurses5 libncurses5-dev g++ libssl-dev libcrypto++-dev $ ./configure --enable-threads --enable-kernel-poll --disable-hipe --without-javac --with-ssl --prefix=/usr/local/erlang/R15B02 $ make $ sudo make install $ sudo ln -s /usr/local/erlang/R15B02/bin/* /usr/local/bin/
さすがにコンパイルには時間かかったので、ほっといて寝てましたが、翌朝見たら無事完了してました。
試しに自分が書いたコード落としてみても問題なくコンパイル、動作しました。
あとはこんなの書いてプロセスがたくさん起動してみたりとか
process_test.erl
-module(process_test). -export([go/1]). %% 実行開始 go(Count) -> %% 引数で渡された数のプロセスを新たに生成する Fun = fun(_) -> spawn_link(fun() -> recv() end) end, PidList = lists:map(Fun, lists:seq(1, Count)), %% 全てのプロセスにメッセージを送信 send_messages(PidList), io:format("message sent to ~p processes.~n", [Count]), %% 全てのプロセスからの応答メッセージを受信 recv_messages(Count), io:format("all message returned, ok.~n"). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 基底パターン。全てのプロセスにメッセージ送信完了 send_messages([]) -> ok; %% 再帰しながら各プロセスにメッセージを送信 send_messages([Pid | TailPid]) -> Pid ! {hello, self()}, send_messages(TailPid). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 基底パターン。全てのプロセスから応答メッセージを受信完了 recv_messages(0) -> ok; %% 再帰しながら各プロセスからの応答メッセージを受信 recv_messages(Count) -> receive {ok, _Pid} -> ok end, recv_messages(Count - 1). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 生成されたプロセス側で実行されるメッセージ受信待機関数 recv() -> receive {hello, From} -> From ! {ok, self()} end.
実行結果葉こんな感じ。
shin@beagle-ubuntu:~/src/process_test$ erl Erlang R15B02 (erts-5.9.2) [source] [async-threads:0] [kernel-poll:false] Eshell V5.9.2 (abort with ^G) 1> c(process_test). {ok,process_test} 2> 2> timer:tc(process_test, go, [1000]). message sent to 1000 processes. all message returned, ok. {88348,ok} 3> 3> 3> timer:tc(process_test, go, [10000]). message sent to 10000 processes. all message returned, ok. {966369,ok} 4> 4> 4> timer:tc(process_test, go, [30000]). message sent to 30000 processes. all message returned, ok. {1426422,ok}
時間の単位はマイクロセカンドなんで、1万プロセス立ち上げて全てのプロセスにメッセージ送って返ってくるまで約0.9秒。30000プロセスなら1.4秒。
同じことをMacBookPro With Ubuntu だと
Erlang R15B02 (erts-5.9.2) [source] [64-bit] [smp:4:4] [async-threads:0] [kernel-poll:false] Eshell V5.9.2 (abort with ^G) 1> c(process_test). {ok,process_test} 2> 2> timer:tc(process_test, go, [1000]). message sent to 1000 processes. all message returned, ok. {8334,ok} 3> 3> 3> timer:tc(process_test, go, [10000]). message sent to 10000 processes. all message returned, ok. {74601,ok} 4> 4> 4> timer:tc(process_test, go, [30000]). message sent to 30000 processes. all message returned, ok. {225231,ok}
さすがに差があります。クロック数だけでなくコア数の差も効いているのでしょうか。
それでもあれだけの数のプロセスが比較的短い時間で立ち上がるのが確認できました。
もう眠いので今日はここまで。
メモ: Redisをジョブキューとして使う
Redisについて
Redisはいわゆるオンメモリで動作して永続化もしてくれる高速なキーバリューストアですが、ノティフィケーションのような機能ももってます。
実はジョブキューのようなものは無いかなと最初はRabbitMQを調べていたのですが、そういやRedisにそういう使い方できそうなコマンドがあったような。と思ってみてみたらありました。
コマンド
該当のコマンドはPUBLISHとSUBSCRIBEです。
- SUBSCRIBEは現在のコネクションで特定のキーワードの通知が来るのを待機開始するコマンド
- PUBLISHは通知を送信するコマンド
です。
具体的には
SUBSCRIBE foo
とするとキーワードfooで通知を待ち受け、
PUBLISH foo hoge
とするとhogeというメッセージとともにSUBSCRIBEしているコネクションにメッセージを送ります。
実際にコマンドラインからやってみましょう。Redisに付属するクライアントredis-cliを使います。srcディレクトリのナカニあります。
Redisはローカルホストの標準ポート(6379)で動作しているものとします。
まずは待ち受け用のクライアントを起動します。
./redis-cli
"foo"というキーワードでSUBSCRIBEコマンドを発行します。
SUBSCRIBE foo Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "foo" 3) (integer) 1
どうやら待ち受けがはじまりました。
続いて通知送信のためのクライアントを起動します。同じくredis-cliで
./redis-cli
PUBLISHコマンドで通知を送信します。
PUBLISH foo hoge
すぐさま最初のSUBSCRIBEしたクライアントで通知を受け取りました。
SUBSCRIBE foo Reading messages... (press Ctrl-C to quit) ... 1) "message" 2) "foo" 3) "hoge"
どうやらうまくいきました。
それではErlangのRedisクライアントライブラリeredisで試してみます。
eredisでRedisのPub/Subコマンドを試す
さて、eredisを使う場合は普通はeredis:q(Conn, [コマンド | 引数リスト]) などとしますが、待ち受けようにSUBSCRIBEする場合はこれだとエラーが出ます。
ではどうするかというと、eredis_sub_clientというのを使うのですが、この使い方の説明用とでもいうか、eredis_subというものが用意されています。名前からこれが本命かのようですが、あくまでサンプルとしてみた用が良さそうです。が、折角用意してあるので使ってみます。
eredisをmakeした上で、erlコマンドで対話モードを起動します。
erl -pa ebin
起動したらeredis_sub:sub_example() を実行します。
eredis_sub:sub_example().
この中で何をしているかというと、コードはこんな感じになってます。
sub_example() -> {ok, Sub} = start_link(), Receiver = spawn_link(fun () -> controlling_process(Sub), subscribe(Sub, [<<"foo">>]), receiver(Sub) end), {Sub, Receiver}.
- 最初にstart_linkでeredis_sub_client:start_link/6を呼んでgen_serverを起動
- 続いて別プロセスをspawn
- spawnしたプロセス内でcontrolling_process/0を呼んで通知がこのspawnしたプロセスに来るように設定
- 続いてsubscrive/2を呼んで"foo"というキーワードの通知を待つ事をredisに知らせる
- 最後にreceive/1を呼んで再帰ループでメッセージを待機します。
送信はさっきのredis-cliからやってもいいのですが、折角なのでこっちもeredisから。送信側は従来通りのコマンド発行でOKです。
{ok, C} = eredis:start_link(). eredis:q(C, ["PUBLISH", "foo", "helo"]).
PUBLISHした直後に待ち受けていたノードで
received {message,<<"foo">>,<<"helo">>,<0.33.0>}
と表示されれば成功です。
実際には表示だけしてもしょうがないので、ここで受け取ったメッセージからなんらかの処理をしたりする事になると思います。例えば別にリストを用意しておいて、そこになんらかのフォーマットでジョブを入れてからPUBLISHで通知、事前にSUBSCRIBEしていたプロセスが通知を受け取ると同時にリストからジョブを取り出して処理...とかもできそうです。
あと、当然待ち受け側と送信側が別の言語で動作していてもいいですね。ジョブキューのシステムとしてはRabbitMQが有名ですが、ちょっとしたことなら案外こっちが単純で良かったりするかもしれません。