Journal InTime


2014-07-31 (Thu) [長年日記]

_ SECDマシンにおける再帰

先日のICFPCに会社のチームで参加して、コンパイラの実装を少しだけ手伝った。

後でわかったが、ICFPCの仮想マシンの仕様LispKitをベースにしていて、SECDマシンという有名なものらしい。

SECDマシンはスタックマシンで、データスタック、制御スタック(関数呼び出しや条件分岐時に復帰するための情報が積まれる)、環境フレーム(変数が格納される)という三つのスタックを持っている(環境フレームは親フレームへのポインタを持っていて、単純にpopされる代りに参照されなくなったらGCされるので、厳密にはスタックではない)。

Stack/Environment/Control/Dumpという4つのレジスタを持つのがSECDマシンという名前の由来。

例えば、((lambda (x) (+ x x)) 21)のようなプログラムは以下のように表現される。

LDC  21       ; 定数21をスタックにpushする。
LDF  body     ; bodyを本体とするクロージャを生成してスタックにpushする。
AP   1        ; 新しい環境フレームを生成して、21に関数を適用する。
RTN           ; 呼び出し元に戻る。
body:
LD   0 0      ; 0番目の環境フレームの0番目の変数(x)の値をスタックにpushする。
LD   0 0      ; 0番目の環境フレームの0番目の変数(x)の値をスタックにpushする。
ADD           ; スタックから二つの値をpopし、加算した結果をスタックにpushする。
RTN           ; 呼び出し元に戻る。

APは新しい環境フレームを生成して関数を適用するが、APと似た命令に環境フレームを生成しないRAPというものがあり、空の環境フレームを生成するDUMという命令とセットで利用する。

例えば、(letrec ((loop (lambda () (loop)))) (loop))のような再帰的なプログラムは以下のように表現される。

DUM 1         ; 空の環境フレームを生成する。
LDF loop      ; loopを本体とするクロージャを生成してスタックにpushする。
LDF main      ; mainを本体とするクロージャを生成してスタックにpushする。
RAP 1         ; DUMで生成した環境フレームを使用してloopにmainを適用する。
RTN
main:
LD 0 0        ; 0番目の環境フレームの0番目の変数(loop)の値をスタックにpushする。
AP 0          ; 関数を適用する
RTN           ; 呼び出し元に戻る。
loop:
LD 1 0        ; 1番目の環境フレームの0番目の変数(loop)の値をスタックにpushする。
AP 0
RTN           ; 呼び出し元に戻る。

DUMで生成した環境フレームがLDF loopで生成されるクロージャによって捕捉されるところがポイントで、これによってloop内のLD 1 0(loopの適用で環境フレームが積まれるのでLDの第1オペランドは1とする)でloop自身を参照できる。

上記の例でDUM/RAPの代りにAPを使うと、letrecの代りにletを使った場合のように、loop内でloop自身を参照できない。


2014-06-30 (Mon) [長年日記]

_ InfluxDBの古いデータの削除

InfluxDBを試しているが、InfluxDBには今のところ古いデータを自動で削除する機能がない(対応予定はあるようだ)。

古いshardを丸ごと削除するのが効率がよいらしい(InfluxDBでは一定期間ごとにshardが作成される)ので、以下のようなcron用スクリプトを書いてみた。

require "influxdb"

USER = "root"
PASS = "root"
TTL = "1d"

def parse_ttl(ttl)
  case ttl
  when /\A(\d+)s\z/
    return $1.to_i
  when /\A(\d+)m\z/
    return 60 * $1.to_i
  when /\A(\d+)h\z/
    return 60 * 60 * $1.to_i
  when /\A(\d+)d\z/
    return 24 * 60 * 60 * $1.to_i
  else
    raise "invalid TTL - #{ttl}"
  end
end

secs_to_live = parse_ttl(TTL)
influxdb = InfluxDB::Client.new

for shard in influxdb.get_shard_list["shortTerm"]
  end_time = Time.at(shard["endTime"])
  if Time.now - end_time > secs_to_live
    influxdb.delete_shard(shard["id"], shard["serverIds"], USER, PASS)
  end
end

InfluxDBでは小文字で始まる名前のseriesは短期間(short term)、大文字で始まる名前のseriesは長期間(long term)と決まっているが、上記のスクリプトでは短期間のshardだけ削除するようにしている。

ただ、デフォルトでは各shardの期間が7日間になっているので、それよりも短い期間でshardを削除する場合は/opt/influxdb/shared/config.tomlの設定を以下のように変更しておく必要がある(この例では6時間に設定している)。

[sharding.short-term]
duration = "6h"

/opt/influxdb/current/config.tomlの方を変更しても反映されないので注意。

Tags: Ruby InfluxDB

2014-06-29 (Sun) [長年日記]

_ RedisのPub/Subの遅いsubscriber問題

RedisのPub/Subを試していて、subscriber側の受信が遅いとredis-server上のキューが伸び続けて、メモリ使用量が増大し続けてしまう問題にはまった。

以下のようにひたすらpublisher側でひたすらデータを送るようにしておいて、

require "redis"
require "json"

redis = Redis.new(timeout: 0)

redis.subscribe("foo") do |on|
  on.message do |channel, msg|
    i = msg.slice(/(\d+):/, 1)
    p i
    sleep(1.0)
  end
end

subscriber側で以下のようにsleepすると、どんどんredis-serverが太っていく。

require "redis"
require "json"

redis = Redis.new(timeout: 0)

redis.subscribe("foo") do |on|
  on.message do |channel, msg|
    i = msg.slice(/(\d+):/, 1)
    p i
    sleep(1.0)
  end
end

調べてみると、Redis 2.6以降ではバッファサイズの上限を指定でき、上限に達したクライアントは切断されるようだ。

たまたまUbuntu 14.04に上げたところだったので確認すると、redis-server-2:2.8.4-2では最初からredis.confで以下のように設定されていた。

client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

この環境で上記のスクリプトを試すと、たしかに以下のように切断される。

$ ruby sub.rb
"0"
"1"
"2"
"3"
"4"
"5"
"6"
"7"
"8"
"1681"
"1682"
"1683"
"1684"
"1685"
"1686"
"1687"
"1688"
"1689"
/home/shugo/local/lib/ruby/gems/2.2.0/gems/redis-3.1.0/lib/redis/client.rb:233:in `rescue in io': Connection lost (ECONNRESET) (Redis::ConnectionError)

番号が飛んでいるところを見ると、上限に達してもすぐに切断されるわけではないようだ。

結論: ソフトウェアに何か問題があったらとりあえず最新版を確認しましょう。


2014-06-28 (Sat) [長年日記]

_ Ubuntu 14.04へのアップグレード

長らくUbuntu 12.04を使っていたが、重い腰を上げて14.04にアップグレードした(ついでに32bit版から64bit版にしたかったので、正確にはVMのイメージを作り直した)。

インストールは問題なかったが、使い始めるといくつかの問題が…。

  • compizのCPU使用率が高い。→ gnome-session-flashbackを入れてGnome Classicを使用することに。
  • Emacsのバージョンが上がって、T-Codeが動かなくなった。→ 後述の設定で回避。
  • Emacsのバージョンが上がって、insert-signatureがなくなった。 → とりあえずmime-edit-insert-signatureを使用。
  • kb2mb2の起動時にX Error of failed request: BadAccessと言われてこける。 → とりあえず放置。

T-Codeの件については、last-command-charがなくなったせいでgithubの最新のコードでも問題があったので、.emacsに以下のようなコードを入れて凌ぐことにした。

(defmacro advice-last-command-char (commands)
  `(progn
     ,@(mapcar
        (lambda (command)
          `(defadvice ,command (around ,(intern (concat (symbol-name command) "-last-command-char")) activate)
             (let ((last-command-char last-command-event))
               ad-do-it)))
        commands)))

(advice-last-command-char (eelll-key
                           isearch-printing-char
                           isearch-printing-char
                           isearch-printing-char
                           tcode-2byte-alnum-input-method
                           tcode-2byte-alnum-self-insert-command
                           tcode-mazegaki-complete
                           character-to-event
                           isearch-last-command-char
                           tcode-electric-space
                           tcode-insert-ya-outset
                           tcode-katakana-preceding-chars))

defadviceとdefmacro便利。

7/1追記:

insert-signatureについては、.emacsに以下のような設定を追加すれば定義された。

(setq mime-setup-use-signature t)
Tags: Ubuntu

2014-03-15 (Sat) [長年日記]

_ 松江Ruby会議05

松江Ruby会議05に参加してきた。参加されたみなさん、おつかれさまでした。

ライブコーディングがテーマだったので色んな人のコードを見られて楽しかった。まつもとさんも珍しく書き下ろしのコードを紹介してたし。

とくに圧巻だったのはDXRuby作者のmirichiさんのライブコーディングで、15分程度(?)でその場で一からブロック崩しを作られていた。 スプライトの衝突判定にSprite#===を使われていたのが印象的だったけど、たぶんcaseやgrepで使えるようにという設計なんだろう。

自分がライブコーディングで書いたコードは以下のURLに置いておいた。

このコードだと単純なpointcutしか表現できないけど、例えば、

class Foo
  def bar
    Bar.new.baz
  end
end

のようなコードがあった時に、AspectJみたいにwithin(Foo) & call(class: Bar, method: :baz)みたいなpointcutの指定(Fooの中のBar#bazの呼び出しだけにアスペクトを織り込む)ができると使いやすそう。

しかし、Module#prependだとメソッドの呼び出し側でなく呼び出されるメソッドの側を置き換えるので、Fooから呼び出されたかどうかの判定に呼び出し元のクラスの情報が必要になるが、Rubyレベルではその情報を取るAPIがない。 気が向いたらrb_debug_inspector_open()とかを濫用して作るかもしれないけど、呼び出し元のクラスくらいは普通にRubyレベルで取れてもいいように思う。

Tags: Ruby
本日のツッコミ(全3件) [ツッコミを入れる]

_ 宮国 [私は宮崎で木工作家をしている宮国と申します。 現在は仕事の傍ら、以下のウェブサイトを運営しております。 四畳..]

_ shugo [コメントありがとうございます。 レポートを興味深く拝見しましたが、問題意識に共感いただけたことを嬉しく思います..]

_ 宮国 [見ていただいてありがとうございました。コメントもありがとうございます。 卒業論文であのようなレベルのものが書けるの..]