2017年10月23日月曜日

コードに大きな変更を加えずに実行時間を計測するRubyスクリプトの例

SQLを使ったコードを書いていると、PreparedStatementがガンガン増えるので、 実行時間を計測するコードを書くのが結構面倒になります。

コードに散らばったPreparedStatementにBenchmarkやTime.nowを仕込むのは苦行です。 そんな時は以下のようにPreparedStatementにフックするクラスを用意すると計測が楽になるのかな。
require 'sqlite3'

class Statement
  @@total_time ||= 0.0
  @@list ||= {}
  attr_accessor :stmt, :name
  def initialize(stmt, name)
    @stmt = stmt
    @name = name
    @@list[name] = [0.0, 0]  # time, count
  end

  # def method_missing(name, *args)
  def execute!(*args)
    t = Time.now
    # @stmt.send(name, *args)
    result = @stmt.execute!(*args)
    t = Time.now - t

    @@total_time += t
    prev_t, prev_count = @@list[@name]
    @@list[@name] = [prev_t + t, prev_count + 1]
    return result
  end

  def self.total_time; @@total_time; end
  def self.list; @@list; end
end


db = SQLite3::Database.new('test.sqlite')
stmt1 = db.prepare('SELECT * FROM tables')
stmt2 = db.prepare('SELECT foo,bar FROM tables WHERE id=? LIMIT 1')
stmt3 = db.prepare('SELECT * FROM tables WHERE id=?')

#----- 変更はここだけ
script_time = Time.now
stmt1 = Statement.new(stmt1, :stmt1)
stmt2 = Statement.new(stmt2, :stmt2)
stmt3 = Statement.new(stmt3, :stmt3)

# スクリプト全体の実行時間を計測&表示
at_exit {
  puts "name, total, count, avg"
  puts "script, #{(Time.now - script_time).round(6)},,"
  puts "sqlite, #{(Statement.total_time).round(6)},,"
  Statement.list.each {|name, (time, count)|
    puts "#{name}, #{time.round(6)}, #{count}, #{(time/count).round(6)}"
  }
}
#-----

100.times {
  stmt1.execute!
  stmt2.execute!(123)
  stmt3.execute!(456)
}

出力は適当なのでそのままだと見にくいですが、csviewerを使うとこんな感じで表示できます。
$ ruby test.rb | csviewer
+--------+-----------+--------+-----------+
|  NAME  |   TOTAL   | COUNT  |    AVG    |
+--------+-----------+--------+-----------+
| script |  2.956297 |        |           |
| sqlite |  2.953417 |        |           |
| stmt1  |  1.148423 |    100 |  0.011484 |
| stmt2  |  0.008129 |    100 |  8.1e-05  |
| stmt3  |  1.796866 |    100 |  0.017969 |
+--------+-----------+--------+-----------+

今回はexecute!メソッドだけにフックする例を取り上げてみましたが、この登録さえ面倒な場合はコメントアウトしてあるmethod_missingのコードですべてのメソッドにフックすると良いです。このコードは汎用ライブラリとしても使えますが、不要なメソッドまで測定してしまう可能性もあり、実行速度が落ちるデメリットもあります。

Rubyでは意外と使い回せそうなコードなのでコピペ用にメモしておきます。

2017年10月4日水曜日

プロセスの終了を監視するbashスクリプト

ふと nohup command & でタスクを投げた後、別のマシンでその終了タイミングを知る方法がない事に気付きました。 マシン1でタスクを投げた後、作業場を変えたりしてマシン2でそのタスクの終了を待つ必要がある時、地味に困る。 今まではマシン2ではpsを連打したりして終了を確認したりしていたのですが、それもアホらしい作業なので、 プロセスを監視するコマンドを作ってみました。

~/binなどにコマンド登録するほどの代物ではないので、~/.bash_profileに以下を登録。
function pcheck() {
  process=`ps -p $1`
  while [ $? -ne 1 ]; do
    sleep 1
    process=`ps -p $1`
  done
  echo "$1 is finished."
}

使い方は以下。12345はプロセスIDに置換してください。 このコマンドを実行すると、プロセスが終了した時にnohupと同様に、ターミナルに通知が表示されます。
nohup bash -lc "pcheck 12345" &
# nohup pcheck 12345 &  # これでは動かない

~/.bashrcと~/.bash_profileのどちらにコマンドを登録しても、nohup pcheck 12345 & とは動かせないので注意。 また~/.bashrcに関数登録しても動きませんでした。~/.bash_profileに書く必要があるみたいです。

2017年10月2日月曜日

フリゲ紹介: ボムっと発掘!とれじゃーはんたぁ

ボムっと発掘!とれじゃーはんたぁVIPRPG 2017夏の陣で1位を取っていた作品。 私もこれは非常に面白いパズルゲームだと思いました。



ボムっと発掘!とれじゃーはんたぁは、画像左下に表示されているようなパズルを、主人公のバースティが一筆書きで解いていくゲームです。 数字が一筆書きを連続して描ける時間を示しており、 パズル内のブロックを一筆書きですべて爆破したらお宝ゲットという内容になっています。

このゲームは誰でもクリアできる難易度で、何より爽快感が非常に高いのが良いところだと思いました。 パズルはレベルが上がるごとにどんどん大きく、難しくなっていくのですが、 同時にパズルのヒントを表示する補助アイテムも手に入るようになっています。 補助アイテムを使った場合は難易度は最初から最後までほぼ一緒くらい。 大きなパズルでもあまり迷わずにクリアできるので、結構な爽快感があります。

最初のOPが長い(笑)と思いましたが、ゲームが始まってからはサクサクプレイできて、非常に印象の良かったゲームです。おすすめ。

フリゲ紹介: SWORD II マーヴの逆襲

SWORD II マーヴの逆襲はいくつかのサイトで絶賛されていたのでプレイしてみた作品。 2003年の作品ながらこれも最近存在を知りました。

王道的なRPGで10時間くらいのボリュームがある作品でした。 OP画面(左下)はそっけないですが、マップチップや敵グラやは完全自作(右下)との事で中身は気合が入っている。



それ以上にこの作品を印象付けているのはストーリーと細かな作り込み。 序盤早々で封印された邪神マーヴのご神体(左下)を拝めるのですが、このマーヴの封印を巡る物語となります。 ストーリーはなかなか重く、サクサクと味方が死んでいきます(右下)が、 そう来るかという驚きの進展も多く、また最終的には綺麗にまとまっていて、実に良くできているストーリーだと感じました。 お使い的なところも結構ありましたが、それを感じさせないのは良く練られている証拠でしょうか。 ストーリーはテンポ良く進むのでストレスもなく、サクサクとプレイできました。



長時間プレイしても飽きさせない仕組みが随所に施されている点も印象に残りました。 具体的には4番目の仲間との掛け合いや、作中で何度も登場するトトエルは面白い。 戦闘自体もシンプルながら、中盤くらいから細かな部分でスパイスが効いており、完成度の高さを感じます。 ネタバレが多くなり過ぎる気がするのであまり書けませんけど。

唯一キツめだったのはエンカウント。 完全なランダムなようで、1歩歩いて戦闘を3回くらいこなす事も。 さすがにそんな事は滅多に起きないのですが、回復を忘れて悲惨な事にならないよう注意が必要です。 昔ながらのRPGに慣れている人には見慣れた光景ですが、最近の人にはキツめかも。

昔ながらのRPGを感じるには非常に良い作品で、絶賛も頷ける作品だったと思います。

フリゲ紹介: Win' Wind' Windy

Win' Wind' WindyVIPRPG GW祭り2016の作品。敵に触れると一発でやられるウィンディをゴールまで導くシンプルなアクションパズルです。



画面内をゆっくり歩くウィンディがゴールに辿り着けるよう、方向転換やブロック設置をしていく内容。 ハードモードではウィンディがよりウィンディらしくスペランカー化します。

昔はパズルゲーはあまりやらなかったのですが、最近は面白いパズルゲーが増えてきていて、結構プレイしています。 Win' Wind' Windyは誰でもクリアできる難易度で(おまけは結構難しい)、 サクッと1-2時間くらいでプレイできて面白かった作品です。

この「誰でもクリアできて」、「爽快感や達成感がある」という2つを押さえた作品は、 私のような下手くそプレイヤーには重要で、こんなん無理でしょ…というパズルゲームより満足度が高いです。