wassup?

新ブログ→memo.wass80.xyz

百舌谷さんのMVが最高な件

2018-12-19に公開されたこのMVを見てほしい…

www.nicovideo.jp

かっこよすぎる。

百舌谷(もずや)さんを知ったのはこの曲のMV。 www.nicovideo.jp

tilt-sixさんのchiptuneの名曲である。 このPVも百舌谷さんのものであった。 2013年の映像なんですが、今でもめっちゃ新しく感じてしまう。

2016年にもコラボをしていて、またこのMVがめちゃくちゃ力が入っている。

www.nicovideo.jp

3Dの技術面白いですね。

エレクトロサチュレイタ (feat. 初音ミク)

エレクトロサチュレイタ (feat. 初音ミク)

アルコールって美味しいんですか?

お酒に弱いわけではない。お酒の影響はあまり受けない。ただちょっと内向的になってしまう。

お酒を楽しむ上でエタノールの美味しさをあまり理解していないのが問題らしい。ワインってもともとぶどうジュースが美味しいのに、その糖分をエタノールに変換して美味しさを減らしてしまってるように思う。でも、その発酵過程においてできる香りは嫌いではない。

同じことは日本酒にも言える。確かに日本酒の香りはいい匂いである。酒粕も好物である。ただ味はただのエタノールと残った糖分の味しかしないのでは?人間はいかに香りに騙されるかよく知っている分、味そのものはよくわからなくなる。

蒸留酒は更に難しい。味は殆ど失われて、香りしか残ってないんじゃないだろうか。ウイスキーにしたって、香りは楽しい。ただ味はエタノールでしかないように思えてしまう。

ビールが一番おいしさの評価ができない。飲みやすいか飲みにくいかでしか判断できない。苦いのが苦手でもないので、どんなビールでも飲むことはできる。

香りも含めて味という主張はわかる。しかし、その上でエタノールが味にプラスの働きをもたらしているか、わからない。 ごく少量のエタノールが清涼感を与えることはよく知られている。製菓に用いるラム酒などはそれを狙っている。だが、特に10%を超えるエタノールは雑味に感じてしまう。これは僕の舌が悪いんだろうか。

お酒が嫌いなわけでは決してなく、人のお酒をまずくしたいとも思っていない。カクテルを作ったことがあるが、楽しかった。ただ、お酒に対して真剣に考えると、このような雑念がどうしても振り払えない。

誰か美味しいお酒を教えてほしい。

かな配列を自分のSlackの発言から作る

このエントリーはKMC Advent Calendar 2018の14日目の記事です。

adventar.org

13日目の記事は以下の記事でした。

blog.pastak.net

こんにちは。4回生になり、卒論の進捗に追われているwass80です。

キーボードの自作が流行っていますね。 自作は大変なので、今回は今あるキーボードで早く入力する方法を考えます。

tl;dr

  • とりあえず手でかな配列を作って試す。
  • 自分のSlackの発言から1-gram, 2-gramを求める。
  • 自分にとって押しやすいかな配列を焼きなましで求める。

かな入力

ローマ字での入力ではなく、50音での入力です。 多くのキーボードに書かれている「JISかな」による入力が最も普及しています。 ローマ字入力では多くが1文字2打鍵(ha, ki)ですが、 かな入力では多くは1文字1打鍵(は, き)なので有利と考えられます。

しかし、キーボードに書かれている「JISかな」は、あまり最適化されていないように感じます。 なので、この配列をいじることで、より楽なかな入力について考えます。

かな配列の種類

いろいろなカナ入力配列 様々なかな配列についてはここが詳しいです。

  • 用いるキーの数と配列
  • シフトキーの数と種類

によって様々配列が提案されています。

3段配列、4段配列

使うキーの数は主に段数で示されます。 手元の「JISかな」を見るとわかるように、 「JISかな」は上から1段目数字のキーも使って全部で4段のキーを使っています。

有名な「親指シフト」は3段配列として知られています。

FUJITSU COMPONENT LIMITED - FKB7628-801.JPG
By udfoto - 自室にて撮影, CC 表示-継承 3.0, Link

更に極端な例だと2段配列や1段配列があります。 しかしこの場合、使えるキーが20個程度かより少ない数になります。 原則1キーに1かなを対応させる、かな配列の入力をするのには困難性が伴います。

シフトキー

たとえ4段配列にして、すべてのキーをかなに対応させても、キーは足りません。 入力したい文字は84ほどあります。

ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんー

そのため、配列のモードを切り替え、別の配列を入力する方法が必要になります。

前シフト、後シフト

入力したい文字の前(または後)にシフトキーを押すことで、別の文字を入力します。 お手元の「JISかな」では「゛」「゜」のキーが後シフトです。

「か」+「゛」=「が」

となります。

同時シフト

同時シフトは英字キーボードでのシフトキーと同じものです。 シフトキーが押されている間、別の文字が入力されます。

左右シフト

シフトキーが1つでは足りない場合は、シフトキーに左右で別の役割をもたせます。 「親指シフト」ではキーを押した時、同じ側のシフトか、反対側のシフトかで別の文字が入力されます。

とりあえず手でぽちぽち作った配列

かなの頻度表(=1gram)を見ながら、とりあえず手で配列を作りました。 参考: 漢直ノート ひらがな1-gram表

f:id:wass80:20181212200331p:plain
α

仕様

  • Shiftキーは英数入力のためになるべく用いない。
  • 濁点キーを前入力する。
    • ゛+た=だ
  • 濁点キーにより大小も反転する。
    • ゛+っ=つ
  • 二文字書かれているキーはその2文字が清濁の関係とみなす。
    • ゛+め=ぬ
  • Shift ゛+ は = ぱ
  • 1+せ=8

考えたこと

  • 頻度の高い文字を押しやすい上から3段目におく。
  • 濁点キー「゛」が右手側に1つしかないため、清音を左手側に集めている。
    • 左右のキーを交互に用いると速いため。
    • 実は「JISかな」でもそのようになっている。
  • 「が」は高頻度なので、単体でキーが存在する。
  • 「っ」「ゃ」「ゅ」「ょ」が表の配列で小文字。
    • 大文字より小文字のほうが頻度が高いため。
  • あまりに使わない文字を清音の裏(濁点キーで入力)に押し込んだ。

試す

google IMEのローマ字テーブルを作りました。

https://gist.github.com/wass88/3c9e895f9aa872e07cc8c4ddfc2e5b3b/raw/38f92bbf76e4f7967a9acc5469332b792155f446/wass-arr.txt

f:id:wass80:20181212201221p:plain
ローマ字テーブル登録方法

問題点

  • 配列を覚えるのが大変です。
  • あまり頻度順に並んでいないように感じました。
  • シフトキーは両側にないとキーの配列の左右の自由度が失われます。

真面目に最適化を考える

頻度順に並んでいないように感じる理由は、参考にした1-gramが自分の入力したコーパスではないからだと考えられます。 なので、サークルのSlackから自分の発言を抜き出し、自分のコーパスを作ります。 また、2-gram(連続2文字の頻度)を参考にして、 なるべく左右の手が交互に使われる配列を目指します。

python notebookはこちらです。 Keyboard配列 · GitHub

前処理

Slackから発言を取得する

slackclientを使いました。

コード

from slackclient import SlackClient
slack_token = TOKEN
sc = SlackClient(slack_token)

def fetch_msg(page = 1, count = 100):
    try:
        res = sc.api_call(
            "search.all",
            query = "from:@wass80",
            count = count,
            highlight = False,
            page = page
        )
    except e:
        print("Retry")
        sleep(10)
        yield from fetch(page, count)
    if "messages" not in res:
        print("missing", res)
        return
    res = res["messages"]["matches"]
    for r in res:
        yield r["text"]

def fetch_all(count = 3, batch = 100):
    for i in range(1, count + 1):
        print("Fetch: %d" % i)
        yield from fetch_msg(i, batch)
        sleep(10)

with open("msg.txt", "w") as f:
    for m in fetch_all(170):
        f.write(json.dumps(m) + "\n")

発言から記号を取り除く

def remove_symbols(s):
    s = re.sub(re.compile(r"```.+```", re.MULTILINE | re.DOTALL), " ", s)
    s = re.sub(r"<[^>]+>", " ", s)
    s = re.sub(r"`[^`]+`", " ", s)
    s = re.sub(r":[^:]+:", " ", s)
    return s

remove_symbols("hoge<piyo>hoge`piyo`hoge```\npiyo\n```\nhoge\n:piyo:")
'hoge hoge hoge \nhoge\n '

読み方からひらがなに戻す

janomeを使いました。

from janome.tokenizer import Tokenizer
tk = Tokenizer()

def reading(s):
    res = ""
    for t in tk.tokenize(s):
        if t.reading != "*":
            res += t.reading
        else:
            res += " "
    return res

reading("丸竹夷に押御池")
'マルタケエビスニ '

辞書にない単語は無視されますが、あまり問題にはならないでしょう。

1-gram, 2-gramを求める

1-gram

せっかくなので、手で作ったときに参考にした1-gramと比較しました。 文字が自分の1-gramです。丸が参考にした1-gramです。 青は参考より自分の頻度が多い。赤は逆です。

f:id:wass80:20181212183103p:plain
1gram

2-gram

これは、次の項目の1重キー、2重キーの計算結果が反映されています。

f:id:wass80:20181212211533p:plain
2gram*1

配列計算

配列の定義

  • 1重キー(清音のみ)と2重キー(清濁)を定義します。
  • 前シフトキー「゛」の後、2重キーは裏の文字になります。
  • シフトキー「゛」の場所は固定。両側に配置。

2重キーの分解

逆に、表も裏もよく使うキーの場合、分解します。 「かが」→「か」「が」

2重キーの反転

裏のほうがよく使うキーの場合、反転します。 「ゆゅ」→「ゅゆ」

結果

1重キー(頻度順)

'イ', 'ン', 'ウ', 'カ', 'シ', 'ノ', 'ナ', 'タ', 'デ', 'テ', 'ル', 'マ', 'ニ', 'ガ', 'コ', 'ジ', 'リ', 'ョ', 'ア', 'ダ', 'ラ', 'オ', 'モ', 'レ', 'ヨ', 'エ', 'ヲ', 'メ', 'ミ', 'ワ', 'ム', 'ネ', 'ロ', 'ポ', 'プ', 'パ', 'ヅ', 'ペ', 'ピ', 'ヌ',

2重キー

[('キ', 'ギ'),
 ('ク', 'グ'),
 ('ケ', 'ゲ'),
 ('サ', 'ザ'),
 ('ス', 'ズ'),
 ('セ', 'ゼ'),
 ('ソ', 'ゾ'),
 ('チ', 'ヂ'),
 ('ッ', 'ツ'),
 ('ト', 'ド'),
 ('ハ', 'バ'),
 ('ヒ', 'ビ'),
 ('ブ', 'フ'),
 ('ヘ', 'ペ'),
 ('ホ', 'ボ'),
 ('ヤ', 'ャ'),
 ('ュ', 'ユ'),
 ('ー', '〜')]

1重キーの圧縮

1重キーと2重キーの合計がキーの数より多いので、 1重キーの裏に1重キーを置くことで2重キーにします。 これによって「゛」+「シ」=「ワ」など、多少理不尽なことが起こりますが、諦めます。

配列の計算

  • キーの場所をどこにするか。
  • 圧縮された1重キーの場合、裏に回ったキーの場所をどこにするか。 この2つがパラメータとなります。

配列のコスト

3種類のパラメーターをキーの場所ごとに定義しました。

self.press = [100, 10, 0, 20][y] + [1, 0, 0, 0, 1, 1, 0, 0, 0, 10, 20, 30][x]
self.finger = [1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 8, 8][x]
self.hand = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1][x]
  • pressは押しにくさを表す重み。
  • fingerは押す指。
  • handは押す手。

最終的な配列のコストは:

  • 各キーのpressとその1_gramから次の値を合計する。
    • front = 1_gram * press * 1gram_w (表キーの押しにくさ)
    • back = 1_gram * press * 1gram_w * back_w (裏キーの押しにくさ)
  • 2_gramから次の値を合計する。
    • 同じ指を連続で使うなら finger = 2_gram * same_finger_w
    • 同じ手を連続で使うなら hand = 2_gram * same_hand_w

それぞれ重み(*_w)は何度か焼きなましして適当に決定しました。

焼きなまし

適当に生成した配列にこの2つの演算を適用します。 上で定義した配列のコストが小さくなるように焼きなましをします。

初期生成

上が表、下が裏です。

@
- - - - -
- - - - - - - - - - - -
  • コスト: 21.629534099116793,
  • 'hand': 0.39129205368876097,
  • 'finger': 0.4639484594384564,
  • 'front': 18.760037452391433,
  • 'back': 2.0142561335981406

焼きなまし後

@
- - -
- -
- - - -
- - - - - - - -
  • コスト: 15.252237067009535,
  • 'hand': 0.3088283757887919,
  • 'finger': 0.20433342691029233,
  • 'front': 13.840631230449489,
  • 'back': 0.8984440338609608

できた配列を試す

  • Google IMEのローマ字テーブルとして出力して試しました。
  • わかったこととして、4行配列は手の動きが大きすぎてやはり面倒です……。
  • 指のもつれとかは起こりにくいのかもしれないです。
  • ただ、キーを全然覚えられる気がしなかったので諦めてしまいました。

phoenix配列

やはり4段配列は手を1段目に持っていくのが面倒です。 手が動かない配列はないかと探すと、phoenix配列が良さそうでした。

phoenix配列は2段配列のアルファベット方式です。 サイトにはgoogle IME用のテーブルが用意されているので、手軽に試すことが出来ます。

phoenix Real Time Input Method

f:id:wass80:20181212204229p:plain
phoenix配列

漢字直接入力も対応しているらしいですが、流石に覚えられない自信があります。

wass-phoenix配列

せっかく自分の1-gramを求めたので、この配列も自分に最適化しました。

f:id:wass80:20181212204401p:plain
wass-phoenix

結局配列を作っても、それを覚えるコストが高すぎて、全然使いこなせません……。 なにかいい方法はあるのでしょうか。

*1:ところでこれ、公開して大丈夫なんでしょうか。完璧な電子シュレッダーによってバラバラになった情報から、元の情報の断片を作ることはできるのでしょうか。例えば、これと辞書をマッチすることで、僕が普段発言してそうな単語が割れそうじゃないですか?こわい。「あび」がちょっと多いの、絶対『メイドインアビス』のせいですよね。んなぁ。

ISUCON8 反省

:thinking_face: 学生枠参加。本戦14位/30

SNSシェアをしなかった中ではそれなりの順位っぽいですね。

https://github.com/wass88/isucon8-final/issues/2

// の後ろが事後反省。

  • /infoの改善 -まともな時系列DBを使うほうが早そう // 時間内に無理
    • キャッシュする→定期的に生成するとかを思いつくべきだった 
    • たぶんここがかなり効くんだと思う
  • 取引測度の改善
    • POST /orders でrunTradeする必要はない。
    • Tradeサーバーをgoで動かす // できなかった。 pythonでもやろうとしたがうまく行かず
    • Tradeのアルゴリズムを改善 // 時間内に無理
  • ISULOGGER の send_bulk を使う。
    • sendをまとめて1リクエストにしてくれるやつ。非同期にログを流す。
    • // Logサーバーを立てて実装した。
    • // +200点ぐらいにはなってくれた気がする。
  • /signinのBAN
    • // 実装間に合わず
    • // SNSをONにしたときに効くらしい。
  • isucon-3のインスタンスが速いらしい
  • setting DBは完全に不要。
    • // redisに置いてもらった。
  • // SNSにはほとんど手を付けていなかった、最後30分でONにしてFailして終わりだった。
    • // 確率的にONにするのは思いつかないな…

SQLの改善はutgwさんに、インフラは全部nonyleneさんにやってもらった。

僕は全体を見て最初の2時間で戦略を練っていた。

/infoの真面目な改善について話し合うべきだった。

問題はすごく良かったと思います。まだまだ手を付けられるところが多い。

Pythonで非同期な処理を書くの非常に面倒なので、次からはGoでやりたい…

毎年要求されるレベルが線形に増えていくので、それに追いつかない感じがある。頑張り。

Rubyのtrace命令の話を確かめる

過去のblogから移動しました 元公開日時 2017-12-31 06:10:19

こんばんは。
この記事はKMCアドベントカレンダー23日目の記事です。大遅刻です。

前日の記事はprimeさんの
ビット演算マニアのためのAVX512入門 【KMCアドベントカレンダー 22日目】 - prime's diaryです

明日の記事はtetsutalowさんの
3つの事件で振り返る「何をやったらウイルス罪で捕まるか2017」 - Tetsu=TaLowの雑記(はてブロ版)です

本題

Ruby 2.5 の改善を自慢したい

インターンの講義でお世話になった笹田さんの上の記事を読みました。
その記述を確かめながら小ネタにしようと思います。

次の素朴なコードのベンチマークを見てください。

require 'benchmark'
Benchmark.bmbm 10 do |r|
  r.report "Normal" do
    1000000.times {
      a = 0
      b = 1
      100.times {
        c = a + b
        a = b
        b = c
      }
    }
  end
  r.report "Boost" do
  boost %q(
    1000000.times {
      a = 0
      b = 1
      100.times {
        c = a + b
        a = b
        b = c
      }
    }
  )
  end
  //省略
end
                 user     system      total        real
Normal       9.510000   0.130000   9.640000 ( 10.713862)
Boost        8.590000   0.090000   8.680000 (  9.021931)

Boostのほうが速いようです。
boost %q()で囲むだけでちょっと速くなっているようにみえます。

ねたばらし

boostの実装は以下のとおりです。

def boost src
  eval src.gsub("\n", ";")
end

引数の文字列の改行全てをセミコロンに置き換えて実行するだけです。

すなわち次のコードより

a = 1 + 2
b = 3 + a

このコードの方がちょっと速くなるということです

a = 1 + 2;b = 3 + a

なんでだろう?

結論としては, trace命令が減ったからです。

RubyVM

YARV

VMコードを見るには

  puts RubyVM::InstructionSequence.compile("1+1").disasm

のようにすれば"1+1"のVM命令が読めます。

== disasm: #<ISeq:<compiled>@<compiled>>================================
0000 trace            1                                               (   1)
0002 putobject_OP_INT2FIX_O_1_C_
0003 putobject_OP_INT2FIX_O_1_C_
0004 opt_plus         <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0007 leave

細かいところは説明しません。行番号と命令が1行ずつかかれています。
雰囲気で1と1をスタックに積んで足し合わせている様子を感じ取ってください。

rubyのコードは実行するときに, このようにVM命令を一度挟んでいます。

命令の説明は
insns.def
にあるので興味があれば読んでください。

trace命令

笹田さんの記事によると, trace命令はRubyコードの各行に挟まれるようです。

実際, ベンチマークで使ったコードのVM命令を見るとtrace命令はたくさんあります。

1000000.times {
  a = 0
  b = 1
  100.times {
    c = a + b
    a = b
    b = c
  }
}
== disasm: #<ISeq:<compiled>@<compiled>>================================
== catch table
| catch type: break  st: 0002 ed: 0008 sp: 0000 cont: 0008
|------------------------------------------------------------------------
0000 trace            1                                               (   1)
0002 putobject        1000000
0004 send             <callinfo!mid:times, argc:0>, <callcache>, block in <compiled>
0008 leave
== disasm: #<ISeq:block in <compiled>@<compiled>>=======================
== catch table
| catch type: break  st: 0014 ed: 0020 sp: 0000 cont: 0020
| catch type: redo   st: 0002 ed: 0020 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0020 sp: 0000 cont: 0020
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] a          [ 1] b
0000 trace            256                                             (   1)
0002 trace            1                                               (   2)
0004 putobject_OP_INT2FIX_O_0_C_
0005 setlocal_OP__WC__0 4
0007 trace            1                                               (   3)
0009 putobject_OP_INT2FIX_O_1_C_
0010 setlocal_OP__WC__0 3
0012 trace            1                                               (   4)
0014 putobject        100
0016 send             <callinfo!mid:times, argc:0>, <callcache>, block (2 levels) in <compiled>
0020 trace            512                                             (   9)
0022 leave                                                            (   4)
== disasm: #<ISeq:block (2 levels) in <compiled>@<compiled>>============
== catch table
| catch type: redo   st: 0002 ed: 0026 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0026 sp: 0000 cont: 0026
|------------------------------------------------------------------------
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] c
0000 trace            256                                             (   4)
0002 trace            1                                               (   5)
0004 getlocal_OP__WC__1 4
0006 getlocal_OP__WC__1 3
0008 opt_plus         <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0011 setlocal_OP__WC__0 3
0013 trace            1                                               (   6)
0015 getlocal_OP__WC__1 3
0017 setlocal_OP__WC__1 4
0019 trace            1                                               (   7)
0021 getlocal_OP__WC__0 3
0023 dup
0024 setlocal_OP__WC__1 3
0026 trace            512                                             (   8)
0028 leave

trace 1 というのがコードの行ごとに挟まるVM命令です。
この命令が7回も現れているのがわかります。

これに対して, コードの改行を削除して以下のように(広義の)ワンライナーにした場合, VM命令はこうなります。

1000000.times {;  a = 0;  b = 1;  100.times {;    c = a + b;    a = b;   b = c;  };};
== disasm: #<ISeq:<compiled>@<compiled>>================================
== catch table
| catch type: break  st: 0002 ed: 0008 sp: 0000 cont: 0008
|------------------------------------------------------------------------
0000 trace            1                                               (   1)
0002 putobject        1000000
0004 send             <callinfo!mid:times, argc:0>, <callcache>, block in <compiled>
0008 leave
== disasm: #<ISeq:block in <compiled>@<compiled>>=======================
== catch table
| catch type: break  st: 0010 ed: 0016 sp: 0000 cont: 0016
| catch type: redo   st: 0002 ed: 0016 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0016 sp: 0000 cont: 0016
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] a          [ 1] b
0000 trace            256                                             (   1)
0002 trace            1
0004 putobject_OP_INT2FIX_O_0_C_
0005 setlocal_OP__WC__0 4
0007 putobject_OP_INT2FIX_O_1_C_
0008 setlocal_OP__WC__0 3
0010 putobject        100
0012 send             <callinfo!mid:times, argc:0>, <callcache>, block (2 levels) in <compiled>
0016 trace            512
0018 leave
== disasm: #<ISeq:block (2 levels) in <compiled>@<compiled>>============
== catch table
| catch type: redo   st: 0002 ed: 0022 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0022 sp: 0000 cont: 0022
|------------------------------------------------------------------------
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] c
0000 trace            256                                             (   1)
0002 trace            1
0004 getlocal_OP__WC__1 4
0006 getlocal_OP__WC__1 3
0008 opt_plus         <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0011 setlocal_OP__WC__0 3
0013 getlocal_OP__WC__1 3
0015 setlocal_OP__WC__1 4
0017 getlocal_OP__WC__0 3
0019 dup
0020 setlocal_OP__WC__1 3
0022 trace            512
0024 leave

わかりにくいと思いますが, 行trace命令は7つから2つになり, 5つも減っています。
このため, 実行がすこし速くなったわけです。

trace命令の定義

trace命令の定義はinsns.defにあります。

nopほど単純ではない処理が行われているのがわかります。
記事に説明されていたとおり, TracePointという機能で使われているようです。

もうちょっと検証

最初のベンチマークの時にもう少し検証をしています。
boostの定義とほとんど同じですが,
セミコロンではなく元の改行のままにするnonboostを定義しました。

def nonboost src
  eval src.gsub("\n", "\n")
end

boostnonboostの違いは";""\n"だけです。
このベンチマークは以下のようになりました。

Rehearsal ----------------------------------------------
Normal      10.420000   0.210000  10.630000 ( 13.289735)
Boost       10.280000   0.190000  10.470000 ( 12.934161)
NonBoost    10.750000   0.200000  10.950000 ( 13.520492)
------------------------------------ total: 32.050000sec

                 user     system      total        real
Normal      10.260000   0.160000  10.420000 ( 11.629897)
Boost        8.970000   0.120000   9.090000 (  9.784410)
NonBoost     9.820000   0.140000   9.960000 ( 11.012133)

何度か実行してみると, Boost <<< NonBoost < Normal の順に遅い感じがしました。
明らかにNonBoostは余計な処理を行っているのにも関わらず, 遅くならないのは不思議です。

ruby2.5の場合

このベンチマークは以下のruby2.4で実行しましたが,

ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin16]

次のruby2.5では続く結果のように, どれもほとんど差がありません。

ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin16]
Rehearsal ----------------------------------------------
Normal      12.007801   0.255304  12.263105 ( 15.818664)
Boost       12.588323   0.237480  12.825803 ( 15.782480)
NonBoost    12.292849   0.235013  12.527862 ( 14.568634)
------------------------------------ total: 37.616770sec

                 user     system      total        real
Normal      11.921363   0.238929  12.160292 ( 13.990406)
Boost       11.551764   0.235187  11.786951 ( 13.314673)
NonBoost    11.725629   0.180515  11.906144 ( 13.135241)

これは最初の記事で紹介されている通り, trace命令が抑制される最適化が施されたためです。

実際の命令を見てみると, 1つもtrace命令が存在しません。

== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(9,5)>==================
== catch table
| catch type: break  st: 0000 ed: 0006 sp: 0000 cont: 0006
== disasm: #<ISeq:block in <compiled>@<compiled>:1 (1,14)-(9,5)>========
== catch table
| catch type: break  st: 0007 ed: 0013 sp: 0000 cont: 0013
== disasm: #<ISeq:block (2 levels) in <compiled>@<compiled>:4 (4,16)-(8,7)>
== catch table
| catch type: redo   st: 0001 ed: 0019 sp: 0000 cont: 0001
| catch type: next   st: 0001 ed: 0019 sp: 0000 cont: 0019
|------------------------------------------------------------------------
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] c
0000 nop                                                              (   4)[Bc]
0001 getlocal_OP__WC__1 a                                             (   5)[Li]
0003 getlocal_OP__WC__1 b
0005 opt_plus         <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0008 setlocal_OP__WC__0 c
0010 getlocal_OP__WC__1 b                                             (   6)[Li]
0012 setlocal_OP__WC__1 a
0014 getlocal_OP__WC__0 c                                             (   7)[Li]
0016 dup
0017 setlocal_OP__WC__1 b
0019 leave                                                            (   8)[Br]
| catch type: redo   st: 0001 ed: 0013 sp: 0000 cont: 0001
| catch type: next   st: 0001 ed: 0013 sp: 0000 cont: 0013
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] a          [ 1] b
0000 nop                                                              (   1)[Bc]
0001 putobject_OP_INT2FIX_O_0_C_                                      (   2)[Li]
0002 setlocal_OP__WC__0 a
0004 putobject_OP_INT2FIX_O_1_C_                                      (   3)[Li]
0005 setlocal_OP__WC__0 b
0007 putobject        100                                             (   4)[Li]
0009 send             <callinfo!mid:times, argc:0>, <callcache>, block (2 levels) in <compiled>
0013 leave                                                            (   9)[Br]
|------------------------------------------------------------------------
0000 putobject        1000000                                         (   1)[Li]
0002 send             <callinfo!mid:times, argc:0>, <callcache>, block in <compiled>
0006 leave

これによってほぼ任意のRubyのコードが数%速くなったのだと思います。すごい。

注意

記事にあるように, Ruby2.5以前でこの最適化を施したければ,

RubyVM::InstructionSequence.compile_option = {trace_instruction: false}

を使うべきです。

Cookpadインターン参加記 または 東京観光

過去のblogから移動しました 元公開日時 2017-09-01 15:30:00

Cookpadインターン2017に参加していました。
その時の行動ログを写真とともに残しておきます。

人事さんのごはん
Cookpadには社食というものはなく, 代わりにキッチンがあります。インターン前半の講義フェイズでは, お昼ごはんは人事さんが作ってくださいました。どれも美味しかったです。

美味しいカレー
特にこのキーマカレーはとても美味しかったです。残ったカレーを講義終わってから温め直して食べました。講義中はこの事以外考えてなかった気がします。

コミケ
インターンの初週が終わると, はじめてのコミケがありました。初日だけ参加しました。KMCのブースと辺りの技術系ブースを眺め, ボカロCDを何枚か買いあさりました。

ボドゲ会
とあるボドゲ会にお邪魔することができました。東京に来てしばらくボドゲができていませんでした。そのため, 11時間連続でボドゲしたこの日はとても満足しました。

大会
翌日, KMCの仲間とともに謎の大会に参加しました。ボドゲの筆記テストあり, キャプテンリノを高く積み上げ, よくあるジレンマ問題をやり, 交渉のゲームでは無になりました。こういう気楽な遊びは神経を使わなくて本当に楽しいです。

スカイツリー
スカイツリーを下から眺めました。京都タワーとそれほど変わらない気もしました。しかし, 京都駅に帰ったところ京都タワーはめちゃくちゃ低いことに気づきました。

チキンカツ
一番恋しかったのはハイライトのチキンカツですね。代わりになるものを探して一番近かったのは浜勝のチキンカツでした。

唐揚げ
Cookpadのキッチンで唐揚げを作りました。唐揚げはKMCでも作ったことがあるので余裕ですね。オフィスの冷蔵庫にはお世話になりました。

ボス
最終週の休みに上野のクレーンゲームでラッキービーストを手に入れました。その流れでけものフレンズ展にも足を運びました。かばんにボスを入れているとかばんちゃんの気持ちになれます。このボスは実物大でめちゃくちゃでかいので, 僕のかばんのメインスペースを専有しました。最終的にその状態で京都に帰ることになりました。

恵比寿
恵比寿のビルの最上階から見える恵比寿駅です。複々線を眺めるのは楽しいです。後半のお仕事フェイズでは, 外の風景に癒やされていました。

肉
インターン最終日の肉です。おいしい。

インターンの内容を何も書いてない気がしてきました。

やりたいことを見つけられたらいいなと言う気持ちでインターンに参加していました。インターンでやった事自体は自分にとってそれほど大きな価値はなかった気がします。結局やりたいことを見つけられれば, すぐに手に入るものばかりだった気がします。

やりたいことって見つかるものなんですかね…。
とりあえずもっと手を動かさないと何も始まらないと思うので, 夏休みの後半はどうにかします。どうにかしたい。
2年ぶりにエンジニアのコミュニティーに属せて, やる気がでてきたつもりになったので頑張ります。

バイトをやめた

過去のblogから移動しました 元公開日時 2017-08-06 12:37:34

いわゆる退職エントリです。

大学でRとbashで遺伝子データの解析を1年超やってました。
辞めた理由は1ヶ月のインターンに行くためです。
インターンの後も学部3,4回生はそれなりに忙しいのもあります。
学部生は大学で働いても専門性が加味されず,
あんまりお金にならない。

遺伝子学の分野は実験(WET)と解析(DRY)の混合になります。WETとDRY両方に精通している人間は多くないようです。僕はもちろんDRY。実験室は一度見たきり。

マウスやヒトのリファレンスゲノムってかんたんにダウンロードできるんです。ヒトの実際のAやらGやらCやらTやらを眺めるのは面白かったです。自分もこれとほとんど同じなの不思議に感じます。あと, 誰の遺伝子なんだろう。リファレンスゲノムは時々アップデートされるので, 誰か1人のというわけではなさそう。

辛かったのはR言語。Rを使う必要が何故あるのかというと, 生物系のRのライブラリリポジトリがあり, そこのライブラリにはたいてい論文がついています。代わりが効きにくいのです。

Rに触れた最初はかなり苦痛でした。言語仕様と標準ライブラリはもれなくクソです。tidyverseライブラリ群にはかなり救われました。tidyverseとforeach使ってればRはマスターしたと言っていいと思います。(あといくつかの統計的モデルのライブラリが必要)

tidyverseを使う中でRはメタプログラミング言語という事に気づきました。Rの文法は流行らなかったと言われるLispのM式なのです。なので, ifも関数です。ということはRは数少ない遅延評価言語なのです。

実際に一番苦労させられたのはコマンドラインで走る雑多なDRYツール群です。Bashで長大な実験パイプラインを作るのに苦労しました。前任者の悲惨なコードを目の当たりにしていたので, かなり気を使いました。DRYの実験でgitで管理してる人間は存在するのだろうかみたいな世界観です。大変気を張ります。

雑多なツール群でめんどくさいのは, コマンドラインの引数のパーサが色とりどりすぎる点です。ハイフンが1個だったり2個だったり。引数の数が5を超えると覚えてられずにすぐに忘れます。必須なオプションって矛盾してると思うんですがどうなんでしょう。コマンドラインオプションを間違えると1,2日走らせていた実験が失敗するのも悲しいですね。シェルスクリプトの強いLinterがほしい。

色々愚痴りましたが, 別の世界観を垣間見れたのは悪いことでなかったと思います。明日から普通の情報系のコミュニティーに戻ります。インターン頑張ります。

欲しいものリストをおいておきます
http://amzn.asia/8P20UKl