かな配列をつくる

このエントリーは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

ICPC国内予選2017参加記

過去のblogから移動しました 元公開日時 2017-07-19 17:07:17

こんにちは。3回生のwassです。
ICPCに参加したのでそのログを残しておきます。

僕は競プロから1年以上退いて,
活動としては部内のプロコンに月に1回程度参加するぐらいでした。
そのため実装はチームメイトに任せることにしました。

A: やってもらった。

B: やってもらった。焦って誤読しあってしまったのは良くなかった。みんなで同じ問題読む大事さを忘れていた。

C: やるだけ。やってもらった。

D: 困った。全探索解法部分はわかるが, bitDP部分でつまずく。半嘘解法を思いつき実装しているチームメイトを眺めた。オーダーがnだけ余計にかかる解法のため実行に時間がかかっていたらしい。

E: Dを実行しながらEを僕が実装することになった。構文木の全列挙で間に合うことがわかる。構文木のデータ構造を作らずに探索する方法は気づかなかった。構文木を素直に全列挙し, 恒等式判定をする実装をした。実行がコンテスト終了に間に合わなかった。あとで確認すると実装は合ってたっぽい。Dではついてた-O3を忘れていたのは事故。

G: 右手法で探索をすれば行けるやろという気分ではいた。だけ。フローは賢いなあ。。

Dは奇跡的に間に合ったので結果は4完。4完内ではビリ近くの55位。くやしい。

競プロ楽しいですね。活動を再燃させる。

悪いものを食べて判断力が鈍っているときに,
楽しそうな提案をすると乗ってくれることがわかるブログです→
http://kyp.hatenadiary.com/entry/2017/07/19/024104

部室に温度センサーとかつけて監視する

(過去のblogから移動しました 元公開日時 2016-12-22 05:45:44)

こんにちは, KMC2回生のwass80です。

この記事はKMC Advent Calendar 2016 21日目の記事です.
昨日の記事はtronくんの「Neutron 買ってみたのは いいけれど……」でした。記事タイトルが575ですね。
明日の記事はbase64くんの「いい感じのメドレーを自動生成したい」です。自分がもやりたかったことやられたので, 後でいい感じコミットぜったいしたる。

今回はRaspberryPi3 ModelBを買ったので, 使って部室の監視をしたいと思います。

概略図

いい感じ図

Raspberryにつながった温度センサーの値をfluentdでinfluxDBに送りつける。
grafanaでグラフを表示。

用意するもの

RasberryPi3はArduinoと違って, オス-メスのジャンプワイヤーが必要になるので注意しましょう。買い忘れました。

RasberryPi3

センサーをつなげて値を読み取りましょう。

セットアップ

Raspberry Pi 3を買ってMacを使ってWiFi接続とSSHの接続するまで

SDカードにRaspbianを焼いてRasberryPi3に差し込みます。
USBで電源を供給すれば起動します。HDMIで画面を見ます。
初期パスワードはuser:pi/pass:raspberryです。速やかに変更しましょう。
sshがデフォルトで無効になっているので有効化する必要があります。
Wifiでつながると便利なのでその設定もします。

温度センサー

第39回「ラズベリーパイで温度・湿度・気圧をまとめて取得!AE-BME280でIC2通信」

このセンサーにははんだ付けが必要です。
I²C方式シリアル通信をします。

上の記事通りに接続したら, I²Cを有効化します。
Github: SWITCHSCIENCE/BME280のコードを借りて(少し改変して)データを表示してみます。

t, p, h = readData()
print("気温:%f\t大気圧:%f\t湿度:%f" % (t, p, h))
pi@raspberrypi:~ $ python bme280/bme280.py
気温:19.729584     大気圧:1005.779496   湿度:56.260745

動いてそうです。

このデータを10秒おきに次のfluentdに送りつけましょう。

fluent/fluent-logger-python

from fluent import sender
import time

logger = sender.FluentSender('raspi', host='sharp')

if __name__ == '__main__':
        while True:
                t, p, h = readData()
                print("気温:%f\t大気圧:%f\t湿度:%f" % (t, p, h))
                logger.emit('climate', {'temperature': t, 'pressure': p, 'humidity': h})
                time.sleep(10)

systemd用のunitファイルを書きましょう。

#/etc/systemd/system/bme280.service
[Unit]
Description = bme280 climate sensor

[Service]
ExecStart = /home/pi/bme280/bme280.py
Restart = always
Type = simple

[Install]
WantedBy = multi-user.targe
$ sudo systemctl enable bme280
$ sudo systemctl start bme280

fluentd

ログの受け渡しをするサービス。
fluentdは Input → Filter → Output の経路でJSONのログ(event)を流します。

例えば, 以下のことができます。

  • あるログファイルの書き込みを感知して(Input)
  • それがErrorのログならば(Filter)
  • Slackへ通知する(Output)

今回は以下の構成になります。

  • TCPでログを受け取る(Input)
  • そのすべてを(Filterなし)
  • influxDBに送りつける(Output)

InputとFilterとOutputを結びつけるのは, ログに紐づくタグです。
Inputでログにタグを付け, 対応するFilter, Outputが動きます。

fluentdはすでに部室で動いていたので間借りします。

fluentdはデフォルトでTCPで受け取る以下のForward Inputが動いています。

#不要なコード
<source>
  type forward
  port 24224
  bind 0.0.0.0
</source>

RaspberryPIからraspi.climateタグをつけてfluentdに送っています。

#前述抜粋
logger = sender.FluentSender('raspi', host='sharp') #sharpはfluentdのあるサーバ名
logger.emit('climate', {'temperature': t, 'pressure': p, 'humidity': h})

なので以下の設定を追加します。

<match raspi.climate> #このタグであれば
  @type copy #次のOutputそれぞれに受け渡す
  <store> #ファイルに保存
    @type file
    path /var/log/td-agent/raspi/climate.log
  </store>
  <store> #influxdbに送りつける
    @type influxdb
    host  192.168.220.31
    port  8086
    dbname climate
    user  root
    password  root
    use_ssl false
    time_precision s
  </store>
</match>

# matchは上からマッチし, マッチしたものがあればそれ以上マッチしない。
<match raspi.**> #上にマッチしなければ, 別のファイルに保存。
  @type file
  path /var/log/td-agent/raspi/raspi.log
</match>

今回はinfluxDBに送るついでにファイルにも保存していますが, DBを真面目に運用するなら必要ないでしょう。

influxDB

field/tagキーに値を時系列で突っ込んで行くデータベース。

Key Concepts

「性別(∋男,女)」のように値の種類(=カーディナリティ)が少ないものはtagキー。
「気温=実数」のようにカーディナリティが高いものはfieldキーに指定します。

influxDBの準備にはdocker-composeを用いました。

参考: nicolargo/docker-influxdb-grafana

# docker-compose.yml
version: '2'
services:
  influxdb:
    image: influxdb:latest
    ports:
      - "8083:8083"
      - "8086:8086"
    env_file:
      - 'env.influxdb'
    volumes:
      - influxdb-storage:/var/lib/influxdb
  grafana:
    image: grafana/grafana:latest
    ports:
      - "13000:3000"
    links:
      - influxdb
    volumes:
      - grafana-storage:/var/lib/grafana
    environment:
      - GF_SERVER_ROOT_URL=%(protocol)s://example.jp/~wass80/app/grafana
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
volumes:
  influxdb-storage:
    driver: local
  grafana-storage:
    driver: local

起動する。

$ docker-compose up -d

InfluxDB と fluentd を組み合わせを試してみた

データベースを作ればデータを受け取る準備が完了します。
influxDBのWebインターフェースはdeprecatedのようなので, 今回はCLIを用いました。

$ docker ps
CONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS              PORTS                                                                 NAMES
791734f2cf3a        grafana/grafana:latest               "/run.sh"                8 hours ago         Up 8 hours          0.0.0.0:13000->3000/tcp                                               dockerinfluxdbgrafana_grafana_1
d39bac4136e8        influxdb:latest                      "/entrypoint.sh influ"   8 hours ago         Up 8 hours          0.0.0.0:8083->8083/tcp, 0.0.0.0:8086->8086/tcp                        dockerinfluxdbgrafana_influxdb_1

$ docker exec -it d39 influx
Visit https://enterprise.influxdata.com to register for updates, InfluxDB server management, and monitoring.
Connected to http://localhost:8086 version 1.1.1
InfluxDB shell version: 1.1.1
> CREATE DATABASE climate
> SHOW databases
name: databases
name
----
_internal
climate
#fluentdの設定 前述抜粋
  <store> #influxdbに送りつける
    @type influxdb
    host  192.168.220.31 # influxDBの動くサーバ
    port  8086
    dbname climate #データベース名
    user  root
    password  root
    use_ssl false
    time_precision s
  </store>

これで{'temperature': t, 'pressure': p, 'humidity': h}というfleid:値がinfluxdbに流れます。

Grafana

influxDBの内容をめっちゃかっこよく表示してくれるいい子。

先程のdocker-composeで一緒に起動していました。
今回はBasic認証がすでにかかっているところで動かすため, Grafanaの認証を無効化しています。
リバースプロキシ用の設定を環境変数に追加しています。

Grafana: Configuration

# docker-compose.yml (前述抜粋)
  grafana:
    image: grafana/grafana:latest
    ports:
      - "13000:3000"
    links:
      - influxdb
    volumes:
      - grafana-storage:/var/lib/grafana
    environment:
      - GF_SERVER_ROOT_URL=%(protocol)s://example.jp/~wass80/app/grafana
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin

influxDBを登録しましょう。
GUIで設定できます。

あとはめっちゃいい感じGUIでグラフの設定をします。

いいですね。

できました。

凡例の色付き横線を押すと色の変更と軸の左右の変更ができます。

他にも色々センサーを買いましたが, RaspberryPIがアナログ入出力が出来ないことを知りませんでした。A-D変換を買ってきます。
明日の記事をお楽しみに。