Shogo's Blog

Feb 10, 2016 - 2 minute read - aws-lambda python mecab

AWS Lambda で MeCab を動かす(改)

MeCabのPythonバインディングをいじってた関係で、MeCabについてインターネットをさまよっていたら、 AWS Lambda で MeCab を動かすという記事を見つけました。 Lambdaの計算リソースで形態素解析できるのは楽しいですねー。 ただ実装にまだまだ改善できそうな部分があったので修正してみました。 2017/12/06追記 Norio Kimuraさんのコメントを受けて、MeCabをAWS Lambdaで動かす(2017年版)を書きました。 以下の手順でも動きますが、少し簡単に出来るようになっています。 問題点 第一に**「外部プロセスを起動しているので遅い」**という点です。 外部プロセスの起動は非常に重くて数百msかかります。 MeCabは非常に高速で数msもあれば解析が終わるのに、もったいないですよね。 第二に**「OSコマンドインジェクションの危険性がある」**という点です。 解析対象の文字列をコマンドライン引数として渡しており、この際シェルを経由しています。 そのため、{"sentence": "$(ls)"}のような文字列を渡すと、シェルがコマンドとして実行してしまいます。 API Gatewayなどで外部に公開した場合、第三者が何でもし放題な状態になってしまいます。 頑張ってMeCabをライブラリとして呼ぶ 全ての元凶は外部プロセス起動にあるので、頑張ってMeCabをライブラリとして呼んでみましょう。 そもそもなんで外部プロセス起動をしていたかというと、 LD_LIBRARY_PATHが正しく設定されていないためimport MeCab時にlibmecab.soを発見できないからです。 なんとかならないものかと探したところ、Stack Overflowにそれっぽい記事がありました。 Setting LD_LIBRARY_PATH from inside Python 「環境変数を設定してから自分自身をexecし直す方法」と「ctypesを使って絶対パス指定で読み込む方法」が紹介されています。 前者の方がvoteは多いですがLambdaでこれをやるのは大変そうなので、後者で試してみます。 # preload libmecab import os import ctypes libdir = os.path.join(os.getcwd(), 'local', 'lib') libmecab = ctypes.cdll.LoadLibrary(os.path.join(libdir, 'libmecab.so')) 一度読み込んでしまったライブラリは再利用されるため、 import MeCabはここで読み込んだライブラリにリンクされます(importの順番が重要なの闇な感じがする)。 LD_LIBRARY_PATHが正しく設定されている必要はありません。 さて、これでlambda_function.pyとtokenizer.pyが分かれている必要がなくなったので、二つを合体してみましょう。 # coding=utf-8 import os import settings import logging logger = logging.getLogger(__name__) logger.setLevel(settings.LOG_LEVEL) # preload libmecab import ctypes libdir = os.

Feb 6, 2016 - 2 minute read - perl redis

Redisでスコアを複数設定できるランキングを作ってみた

ランキングを作っているとスコアを複数設定したいことがよくあると思います。 例えば「得点が同じだったら早くその得点を出した人優先」とか「勝ち点が同じだったら得失点差が大きい方優先」とかのように、 最初の基準で順位を決められなかった場合の第二基準が欲しいみたいな場合です。 ランキングを作るのにはRedisのSorted Setを使うのが便利ですが、残念ながらSorted Setはひとつしかスコアを設定できません。 少し前にどうやったら実装できるかと社内チャットで話題に上ったので、試しにRedis::LeaderBoardMulti(仮名)という名前で書いてみました。 shogo82148/p5-Redis-LeaderBoardMulti 使い方 メソッドの名前はRedis::LeaderBoardにあわせてありますが、 スコアが複数指定できるようになった関係でちょっと変わってます。 use Redis; use Redis::LeaderBoard; my $redis = Redis->new; my $lb = Redis::LeaderBoardMulti->new( redis => $redis, key => 'leader_board:1', order => ['asc', 'desc'], # asc/desc, desc as default ); $lb->set_score('one' => 100, time); # 第二基準は時間=得点が同じだったら早くその得点を出した人優先 $lb->set_score('two' => 50, time); my ($rank, $score, $time) = $lb->get_rank_with_score('one'); set_scoreの第二引数以降はすべてスコアとして扱われます。(そのためRedis::LeaderBoardと互換性はない) 上の例では「得点が同じだったら早くその得点を出した人優先」になってます。 制限事項 実装の都合により、以下のような制限があります。 スコアはすべて64bit符号付き整数です Redis::LeaderBoardのスコアは倍精度浮動小数点型なので小数も扱えるが、Redis::LeaderBoardMultiは整数だけ Redis 2.8.9以降のみで動きます 実装の仕組み Sorted Setの同じスコアを持つメンバーは辞書順にソートされます(zaddの同じスコアを持つ要素の項を参照)。 例えば以下の様にメンバー「a」「b」「c」を追加すると、必ず「abc」の順番になることが保証されています。 127.0.0.1:6379> ZADD ranking 0 "a" 0 "b" 0 "c" (integer) 3 127.

Jan 23, 2016 - 1 minute read - perl redis

Redis::Fast 0.17 をリリースしました

Redis::Fast 0.17 をリリースしました。 主な変更点は以下のとおりです。 I/Oの待ち合わせに使うシステムコールをselect(2)からpoll(2)に変更 hiredisをv0.13.3にアップデート macでテストが終わらない問題がありましたが、この変更によって修正されています。 hiredisはconnect(2)をnonblokingモードで呼び出しています。 nonblockingなので接続が未完了であってもすぐに制御を返し、errnoにEINPROGRESSが設定されます。 この場合、manにあるようにselect(2)で書き込み可能になるのを待つことで、接続完了を検知できます。 select(2) で書き込み可能になった後に、 getsockopt(2) を使って SOL_SOCKET レベルで SO_ERROR オプションを読み出すこ とにより、 connect() が成功したか、失敗したかを判断できる。 linuxの場合はこれで上手く動くのですが、macだと何故かselect(2)が永遠に制御を返さない場合があるようです。 接続先が存在しない場合に起こるのですが、制御を返す場合もあるので謎です。 いろいろ調べてはみたのですがselect(2)だとどうやっても上手く動かなかったので、poll(2)に変更しました。 poll(2)変更版でテストしてみると、接続先が存在しない場合にPOLLOUTを返すケースとPOLLHUPを返すケースがあるようです。 どうやらPOLLHUPにあたるイベントが来た時の挙動がlinuxとmacとで違うらしい? 謎です。

Dec 22, 2015 - 1 minute read - unity git

UnityのBitmapフォントの収録文字のdiffを取る

Unityで文字を描画するには 「BMFont(Bitmap Font Generator)でビットマップフォントを作る方法」等にあるように ビットマップフォントを自分で作ってあげないといけないらしいです。 (ダイナミックフォントというものもあるらしいけど、まだ安定性が検証ができていないので使ってない。) フォントに入っている全部の文字を収録するとでかくなりすぎるので、一部の文字だけ収録するのが一般的だと思います。 入れる文字は自分で選ぶわけですが、フォントファイルを更新する際に、以前は使えた文字が入っていなくてつらい思いをしたので、 gitで差分をみれるようにしてみました。 gitのいろんなファイル形式の差分を見やすくする方法は Git Diffでcsvの差分を見やすく表示するを参照。 csvのときと同じ要領で、まずはfntファイルをdiffを取りやすい形式に変換するスクリプト(fnt2txt)を用意し #!/bin/bash grep 'char id=' $1 | cut -d' ' -f2 | cut -d= -f2 | perl -MEncode -ne 'printf "%04x: %s\n", $_, encode_utf8 chr($_) if $_ >= 32' fnt2txtを使う設定を.git/configに設定します。 [diff "fnt"] textconv = fnt2txt 最後に拡張子.fntに対してだけこの設定が反映されるようにすればOKです。 *.fnt diff=fnt こんな感じでdiffが見れます。 diff --git a/foo.fnt b/foo.fnt index 79391c0..e262b2d 100755 --- a/foo.fnt +++ b/foo.fnt @@ -93,6 +93,7 @@ 007c: | 007d: } 007e: ~ +00a0: 00a1: ¡ 00a2: ¢ 00a3: £ 事故防止に是非ご利用ください。

Dec 20, 2015 - 1 minute read - python mecab

MeCabをPython3から使う(続報)

Python3からMeCabを扱おうとして挫折していたのですが (MeCabをPython3から使う(中間報告))、 改めて調査して、上手くいかなかった原因が分かったのでご報告します。 おさらい Python3で以下のようにMeCabを使おうとすると import MeCab tagger = MeCab.Tagger('') text = u'MeCabで遊んでみよう!' node = tagger.parseToNode(text) while node: print(node.surface + '\t' + node.feature) node = node.next surfaceが全く読み取れないという現象に遭遇していました。 BOS/EOS,*,*,*,*,*,*,*,* 名詞,一般,*,*,*,*,* 助詞,格助詞,一般,*,*,*,で,デ,デ 動詞,自立,*,*,五段・バ行,連用タ接続,遊ぶ,アソン,アソン 助詞,接続助詞,*,*,*,*,で,デ,デ Traceback (most recent call last): File "m.py", line 10, in <module> print( node.surface + '\t' + node.feature ) UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa3 in position 1: invalid start byte 解決策 詳しい原因なんてどうでもいいからMeCabを使いたい人向けに、最初に解決方法を書いておきます。 以下のように本当に解析したい対象を解析する前に、一度parseをしておけばOKです。 import MeCab tagger = MeCab.

Dec 17, 2015 - 6 minute read - perl

PerlのDBIx::Class利用上の注意点

この記事は、Perl 5 Advent Calendarの17日目の記事です。 Redis::Fast の reconnect についての中で DBIx::Classのreconnectについても触れています。 DBIx::Classの安全にreconnectionが行えるように考慮されていますが、色々と注意点があります。 reconnection周りで調べてみたので、Advent Calendarの枠を借りてまとめたいと思います。 DBIx::Classとは DBIx::ClassはPerlのO/Rマッピングモジュールです。 テーブル間のリレーションを定義でき、JOIN句の入ったクエリもサポートする等、かなり高機能なモジュールです。 もう僕はJOIN句をDBIx::Class以外で書ける気がしません。 詳しくはtypester先生の解説記事をどうぞ。 Perl Hackers Hub 第3回 DBIx::Classでデータベース操作(1) 第3回 DBIx::Classでデータベース操作(2) 第3回 DBIx::Classでデータベース操作(3) サンプル サンプルとしてユーザの所持金を管理する簡単なアプリを作ってみます。 Webアプリとか作るの面倒だったので、コンソールアプリです。 package My::Schema::User { use base 'DBIx::Class::Core'; __PACKAGE__->table('user'); __PACKAGE__->add_columns( id => { data_type => 'INTEGER', is_nullable => 0, is_auto_increment => 1, }, username => { data_type => 'VARCHAR', size => 255, is_nullable => 0, }, ); __PACKAGE__->set_primary_key('id'); # userとmoneyは1対1の関係で、userに対応するmoneyが必ず存在しなければならない __PACKAGE__->has_one( 'money' => 'My::Schema::Money', { 'foreign.

Dec 16, 2015 - 3 minute read - git

git-mergeの挙動をカスタマイズする

最近gitのコンフリクト解消職人みたいになっていてすごくつらいです。 普通のプログラムであれば順番が重要なので手動でのコンフリクト解消は避けられないのですが、 僕が相手にしているのは最終的にMySQLに食わせるデータなのでそこまで順番は重要ではありません。 順番に挿入したところで、MySQLが順番にかえしてくれるとは限りませんからね。 このようなケースではある程度機械的にマージできるのでは?と調べてみました。 merge driver いろいろググってみるとgitattributesでファイル毎にマージの細かい挙動を制御できるようです。 通常マージの方法はgitがよしなに選択してくれますが、merge属性に以下の項目を指定することでマージの方法を強制することができます。 text テキストファイルとしてマージする。 コンフリクトすると <<<<<<<, =======, >>>>>>>でコンフリクトした場所を教えてくれる。 binary バイナリファイルとしてマージする。 コンフリクトするとマージしようとしたファイルを残しておいてくれる。 union テキストファイルとしてマージする。 textと違ってコンフリクトしてもマーカを付けない。どちらの変更も残すように適当にマージしてくれる。 適当なので コンフリクト時の行の順番は保証されない text, binaryはコンフリクトしたときによく見る挙動ですね。 unionは初めて知ったので、簡単なレポジトリを作って挙動を確かめてみました。 $ # masterブランチ上でmembers.txtにAliceを追加する $ git init $ echo Alice > members.txt $ git add members.txt $ git commit -m 'add Alice' [master (root-commit) 8c39714] add Alice 1 file changed, 1 insertion(+) create mode 100644 members.txt $ $ # add-bobブランチ上でmembers.txtにBobを追加する $ git checkout -b add-bob Switched to a new branch 'add-bob' $ echo 'Bob' >> members.

Dec 13, 2015 - 1 minute read - go golang

Goでデプロイ神社書いてみた

Go その2 Advent Calendar 2015の13日目の記事です。 その1 その2 その3 六曜を知ることができる便利コマンドを作ってみたお話です。 Deploy神社とは Maco_Tasuが作ったいつdeployしたら安全かを教えてくれる便利APIです。 詳しくは作者ブログ記事をどうぞ。(Deploy神社APIを作った- 眠すぎて明日が見えない) 便利APIなのですが、依存している外部APIが利用できなくなってしまったため、Deploy神社自体が利用できなくなっています。 作ってみた デプロイする時間が分からないと不便なので、Go実装を作ってみました。 shogo82148/go-deploy-shrine go getしてきてお祈りを捧げればデプロイするべき時間を教えてくれます。 $ go get github.com/shogo82148/go-deploy-shrine/cli/pray $ pray 今日は旧暦の11月3日(先勝)です。deployは午前中に済ませましょう。 先勝 - Weblio 六曜の一。急用や訴訟などによいとされ,早く事を行うのがよく,午前は吉,午後は凶という日。先勝日。せんかち。さきがち。 今日12月13日は先勝で午前中にデプロイするのが良いようです。便利ですね。 六曜とは むかしのカレンダーには暦注と呼ばれる「今日の運勢」みたいなものが記載されていたらしいです。 六曜はその暦注のひとつで、現在のカレンダーにも記載されることの多い影響力の大きなものです。 詳しくはWikipediaで。 六曜 - Wikipedia 旧暦の(月+日)を6で割った余りから簡単に求めることができます。 0: 大安 1: 赤口 2: 先勝 3: 友引 4: 先負 5: 仏滅 旧暦とは 旧暦の月日を求めることができれば六曜は簡単に出せるのですが、 日本における旧暦である天保暦は月の満ち欠けと太陽の動きを元にした暦法であり、 月と太陽の動きを正確に予測する必要があります。 Go版デプロイ神社では「日の出・日の入りの計算―天体の出没時刻の求め方」で紹介されていた計算式を用いています 2033年旧暦閏月問題 天保暦をそのまま当てはめると2033年に月を決定できない問題が知られています。 日本カレンダー暦文化振興協会というところが「閏11月を推奨する」との見解を2015年8月に出しています。 2033年旧暦閏月問題の見解 Go版デプロイ神社では時憲暦方式を採用したつもりです。 せめてGoっぽい話題を 引数に日付を渡すとその日の六曜をかえしてくれます。 いろんな形式に対応していて、以下はすべて2006年1月2日の六曜を返します。 $ pray 20060102 $ pray 1/2/2006 $ pray 2-Jan-06 $ pray 2-Jan-2006 $ pray 2/Jan/2006 $ pray 'Jan 2 2006' 2006-01-02は旧暦の12月3日(友引)です。昼のdeployはさけましょう。するなら朝晩が吉です。 引数の解析には tkuchiki/parsetimeを使っています。 たいていの日時フォーマットなら解析してくれる便利ライブラリです。

Dec 9, 2015 - 4 minute read - perl time leapsecond

Perl の DateTime 利用上の注意点

この投稿は Perl 5 Advent Calendar 2015 の 9日目の記事です。 Perl の Time::Piece 利用上の注意点 という記事の最後にDateTimeへの言及があったのですが、 DateTimeはDateTimeでいろいろとハマりどころがあるんですよね・・・。 僕も今年いくつか罠にハマりました。ちょうどアドベントカレンダーの季節ですし、この機会にハマりどころをまとめてみることにします。 遅い いろんなところで言われていることですが 遅い です。 試しに代表的な日付を扱うモジュールでベンチをとってみました。 (比較のために時間をとるためのPerlの組み込み関数も入れてあります) # いろんな形式で今の時間を取得する use Benchmark qw/ cmpthese /; use Time::HiRes (); use Time::Moment; use Time::Piece (); use DateTime; cmpthese 0, { 'time' => sub { time }, 'Time::HiRes' => sub { Time::HiRes::time }, 'localtime' => sub { () = localtime }, 'Time::Moment' => sub { Time::Moment->now }, 'Time::Piece' => sub { Time::Piece->localtime }, 'DateTime' => sub { DateTime->now( time_zone=>'Asia/Tokyo' ) }, }; Rate DateTime Time::Piece Time::Moment localtime Time::HiRes time DateTime 5303/s -- -95% -98% -99% -100% -100% Time::Piece 103765/s 1857% -- -67% -71% -98% -99% Time::Moment 313599/s 5814% 202% -- -11% -93% -98% localtime 354215/s 6580% 241% 13% -- -92% -98% Time::HiRes 4706723/s 88658% 4436% 1401% 1229% -- -72% time 16536995/s 311751% 15837% 5173% 4569% 251% -- それにしてもTime::Moment速いですね。組み込みのlocaltimeと互角とは。

Nov 23, 2015 - 2 minute read - go golang

Go言語でGraceful Restartをするときに取りこぼしを少なくする

少し前にStarletにGraceful Restartが時たま上手く動かない問題を修正するpullreqを投げました。 原因は割り込みハンドラ内でexitを呼んでいたからでした。 「割り込みハンドラ内ではフラグを建てるだけ」 「メインのプログラム内でそのフラグを見て分岐する」という原則があるのですが、それを守るのは難しいということですね。 (しかし新たな問題を産んでしまいrevertされてしまいましたが・・・ まあ修正後のコードも考え方は一緒です。割り込みホント難しい・・・) このpullreqを取り込んでもらうときに再現実験をやってみたのですが、 Goでもちゃんと動くのかな?と気になったので Go言語でGraceful Restartをするで紹介した プログラムに同じテストをやってみました。 2017-01-22追記: Go1.8以降でGraceful Shutdownがbuild-inになるので、この記事で紹介したライブラリは不要となりました。 詳しくはGo1.8のGraceful Shutdownとgo-gracedownの対応を参照。 mannersでテストしてみる 前回の記事ではmannersとgo-server-starterの 組み合わせが良さそうとの結論になったので、この組み合わせでテストしてみます。 以下テストに使用したコードです。 (今回の内容とは直接関係は無いですが、go-server-starterに変更が入ってFallbackのやり方が前回から少し変わってます) package main import ( "fmt" "log" "net" "net/http" "os" "os/signal" "syscall" "time" "github.com/braintree/manners" "github.com/lestrrat/go-server-starter/listener" ) var now = time.Now() func main() { log.Printf("start pid %d\n", os.Getpid()) signal_chan := make(chan os.Signal) signal.Notify(signal_chan, syscall.SIGTERM) go func() { for { s := <-signal_chan if s == syscall.SIGTERM { log.Printf("SIGTERM!!!!\n") manners.Close() } } }() listeners, err := listener.