Skip to main content

ログ取得ツール

Category: プログラミング

ActiveRecordとSequel

RubyのORMはいくつかあるが、Railsで使われているActiveRecordが最もメジャーで、その次くらいにSequelがあると認識している。 最近ふとしたことで両方を使ってみた感想。どちらも、テーブル名を複数形(sつき)にしなきゃいけなかったり、いろいろ面倒だった。勝手に変化させないで欲しいんだよねー。おれ、プログラム上で語尾を変化させるのは好きじゃないのよね。-erと-orの違いとか、sつけるのつけないの、esやiesになるの、受け身だからedだとか、haveかhasかhadとかhavenとか、isかareか、theをつけるかつけないかとか、datumかdataかなんて、考えたくないじゃん。ただのシンボルなんだから。 そういう細かいアレがあるにせよ、どちらも割と使えたんだけど、ActiveRecordはDB側のconstraintを指定できず、例えばカジュアルにuniqueをつけたいと思ってもできない。Ruby側でvalidationを書くかインデックスにuniqueをつけるかしなければならなかったりして、今のところSequelのほうが使用感は良い。ActiveRecordがモデルの記述を重点しているのに対し、Sequelはモデルがなくてもかなりイイトコまで働ける感じ。 例えばJoinの書き方に関してはSequelのほうが直感的で分かりやすい。現代においてSQLを使うというのはすなわちJoinを使うということであるようなのだが、Sequelはモデルに何も書かなくてもキーをjoinの引数に指定するだけでjoinできる。ActiveRecordはモデルにリレーション(belongs_toだの何だの)をつけないとjoinできない。そもそもjoinじゃなくてjoinsなんだよな…三人称単数現在…そこでそうするか。SQLではJOINSなんて書けないよ。んなこと言うならおめーはActiveRecordsじゃねえのかッコラー!! ただSequelのjoinは同じカラム名のものがあるときに、間違った(と私が思う)結果が返ってきてしまうことに気づいた。意味的に考えてバグっている気がする。 つまり、こうだ。 [pyg language=“ruby”] #! /usr/bin/ruby require ‘sequel’ require ‘pp’ DB=Sequel.sqlite DB.create_table :tbl1s do primary_key :id String :value end DB.create_table :tbl2s do primary_key :id String :value2 Integer :tbl1_id end class Tbl1 < Sequel::Model ; end class Tbl2 < Sequel::Model ; end if $0==__FILE__ Tbl1.insert(:value=>“tbl1data1”) # tbl1s.id=>1 Tbl1.insert(:value=>“tbl1data2”) # tbl1s.id=>2 Tbl2.insert(:value2=>“tbl2data1”, :tbl1_id=>2) # tbl2s.id=>1 Tbl2.insert(:value2=>“tbl2data2”, :tbl1_id=>1) # tbl2s.id=>2

golangのmap[int]intのメモリ効率

最近使うコンパイル言語と言えばgolang。これはこれでけっこうハッピーですね。C++なんて最初からいらんかったんや! channelとgoroutineで並列処理も楽々。ただ適当に書いてるとgoroutineのリークがけっこう起きるみたいですね。

[]intとmap[int]intのどちらを使うか。sparseな感じだとどのくらい違うのかな? []intはだいたい意図したとおりの配列になるのだが、map[int]intはたぶんhash + listかred-black treeになっているだろうから、オーバーヘッドは気になる。

試してみた。


package main

import (
    "flag"
    "fmt"
    "syscall"
)

func main() {
    n := flag.Int("num", 10000, "number of iteratio")
    flag.Parse()
    fmt.Printf("num=%d\n", *n)
    data := make(map[int]int)
    for i := 0; i < *n; i++ {
        data[i] = i + 1
    }
    var r syscall.Rusage
    syscall.Getrusage(syscall.RUSAGE_SELF, &r)
    fmt.Printf("%+v\n", r)
    return
}

./test -num 67108864


num=671088
{Utime:{Sec:24 Usec:232528 Pad_cgo_0:[129 255 255 255]} Stime:{Sec:1 Usec:172404 Pad_cgo_0:[0 0 0 0]} Maxrss:5017321472 Ixrss:0 Idrss:0 Isrss:0 Minflt:1225546 Majflt:0 Nswap:0 Inblock:0 Oublock:0 Msgsnd:0 Msgrcv:0 Nsignals:0 Nvcsw:2759 Nivcsw:1253}

MacOSのgetrusageのMaxRSSはバイト単位。したがって、5017321472/67108864≒74.8。4バイトがキー、4バイトが値だから74.8-4-4で66バイト程度がmapのオーバーヘッドと考えられる。こんなもんか。もし密度が1/20以下くらいのsparseな配列であれば、mapを使うのもいいだろう。

テスト

練習。小括弧()を使わなくてもある程度までプログラムを書けるんだなぁ。Lispと対比して何かひとこと言う必要ある? [pyg language=“ruby”] #! /usr/bin/ruby tbl=[] STDIN.readlines.each do |l| tbl.push l.strip.split end lens=[] tbl.each do |row| row.each_with_index do |col, i| begin if col.length > lens[i] then lens[i]=col.length end rescue NoMethodError, ArgumentError lens.push col.length end end end tbl.each do |row| l=[] row.each_with_index do |col, i| l.push col.rjust lens[i] end puts l.join " " end [/pyg] ある種すごいと思ったのは、配列要素の初期値がnilなので存在しないインデックスを指定するとnilが返ってくるところ。Python時代は例外が発生するからね。ただし、awkなら0と"“の両方として使えるのでいきなり++とかしても数えたりつなげたり処理できるが、Rubyはそうはいかず、nilとintの演算で例外が発生する。結局if文を入れるか例外を拾うかのどちらか… そして、nil > 10 はNoMethodError、10 > nilはArgumentError例外が発生する。違うんだこれが。

2つのファイルに記述されている数値の差を表示する

dfの値の変化だけを見たい場合とか、もっと複雑な書式のテキストデータで数の変化だけを見たい場合、というものが存在する。 試しに書いてみた。 [pyg language=“python”] #! /usr/bin/python

## encoding: UTF-8

import os,sys import difflib import re def isnum(s): if len(s)==0: return False if s[0] in ("+", “-”): return s[1:].isdigit() return s.isdigit() def main(args): p=re.compile("([+-]?[0-9]+)") f1=p.split(file(args[0]).read()) f2=p.split(file(args[1]).read()) diff=difflib.ndiff(f1, f2, charjunk=lambda f: f.isspace()) d=filter(lambda f: f[0] in " +-", diff) nm,np=[],[] for i in d: hdr,s=i[0],i[2:] if hdr=="-" and isnum(s): nm.append(int(s)) elif hdr=="+" and isnum(s): np.append(int(s)) else: for im, ip in zip(nm, np): sys.stdout.write(str(ip-im).rjust(len(s))) if i[2:].isdigit(): sys.stdout.write(“0”.rjust(len(s))) else: sys.stdout.write(s) nm,np=[],[] for im, ip in zip(nm, np): sys.stdout.write(str(ip-im)) if __name__=="__main__": main(sys.argv[1:]) [/pyg] 例えば、以下のように使う。

zsh# python numdiff.py =(df) =(sleep 5; df)
Filesystem           0K-ブロック    使用   使用可 使用% マウント位置
/dev/vda0                    0    4   -4   0% /
tmpfs                        0         0         0   0% /dev/shm

5秒で/は4KB増えている。

簡単なプログラムの話

簡易的にコンパイル型言語でプログラムを作ることがちょくちょくある。まあライブラリの性能評価とか、小さなカーネルモジュールとか、要するにCやC++で書いてソースが1ファイルで済むようなもの。

でもコンパイルオプションが面倒だったりするわけだ。Makefileとか別ファイルにコンパイル方法を書くのは面倒(要するにプログラムの規模にそぐわない手間がかかる)。しかもこのコンパイルオプションというやつ、とてもじゃないけど覚え切れないので、日をまたいだりシェルのヒストリから消えたりすると最悪です。

これまではMakefileが面倒なときはbuild.shみたいなスクリプトにコンパイルするためのスクリプトを書いていたのだけど、最近やっと画期的に便利な方法を発見した。

それはソースにコンパイル方法を書いておいて、ソースごと小さなスクリプトに食わせる、というもの。

例えばEmacsでこういうものがありますね、以下のような記述をファイルの末尾に書いておくという方法。末尾1KBしか読まないとか、^Lがあれば^Lの後ろだけ有効とか、ルールがいろいろあるようですが。

Djangoのルーチンワーク

Djangoでadminとadmindocを使うまでのところ。まだDjangoで遊ぶ気になったというわけではないんですが(おれWeb系の人じゃないし)。

  • django-admin startproject $project_name
  • cd $project_name
  • ./manage.py startapp $app_name
  • vi settings.py
    DATABASE_ENGINE=’sqlite3’
    DATABASE_NAME=’/tmp/test.db’  (例えば)
    TIME_ZONE=’Asia/Tokyo’
    LANGUAGE_CODE=’ja-jp’
    (INSTALLED_APPSに’django.contrib.admin’と’django.contrib.admindocs’を追加。sの付け忘れに注意)
  • vi urls.py
    (最初の方の2行のコメントを外す)
    from django.contrib import admin
    admin.autodiscover()
    (urlpatternsのadmin/doc/とadmin/のコメントを外す)
  • ./manage.py syncdb
    (質問に答える)
  • ./manage.py runserver 0.0.0.0:8000

あとはブラウザでアクセス。このままだと、/はエラーが出るが/admin/にはアクセスできるし、/admin/docへのリンクもできてアクセス可能。UIは普通に日本語が表示される。ログインはCookieの受け入れに注意。

OpenMPのreduction

やっぱ今後やたらにコアは増えまくるから困るねえ、と思って並行コンピューティング技法(Amazon)という本を買ってパラパラと読んでみたが、reductionの処理の並列化は面倒だからIntel TBBやOpenMPのreductionを使うべし、と書いてあった。 実際はOpenMPのreductionは制限がきつくてまともに使える気はしない。任意の関数や式でreduceしたいのだが、+や-などだけ(しかもC++のoperator+すらダメ)ではほとんど何もできない。かと言って普段はせいぜい8コアくらいまでしか使わないのにreductionの並列化なんて毎回やってられない。ただ、重いreduceをしたいことも時々あり、そういうときは並列化したくなることも。intの+や-といった軽いreduceを並列化したところで効果はほとんどないので、やはりOpenMPのreductionの仕様は馬鹿だと思う。 Intel TBBもけっこう単純な内容でSEGV食うことがあるので、あまり頼りになる感じがしない。STLだとvalgrind経由で動かしても大丈夫なプログラムなんだけどな。 本にチラッと書いてあったのだけど、Cilk++というのがあるらしい。調べてみるとC++ベースで、かなり単純な構文でマルチコアを生かせる。やってることはOpenMPよりも単純で、構文が3つしかない。forループ、それから再帰で書いたプログラムを得意としているような感じに見える。今はIntelが買い取ってプッシュしている。ただバイナリ配布しかないし、あまり気軽に試す気になれないな。 そう言えばPythonのスレッドはGILのせいで性能が出ないんですよね。そもそも演算性能を目指していないということもあるのだろう。2.6でmultiprocessingが入って巻き返そうとしているけど、あんまり流行ってないね。

suggest

OpenSearchのxmlファイルにはGoogleやAmazonのようにsuggestをするための記述を書くことができる。そのURLにアクセスするとJSON形式で結果が返り、サジェストされるというもの。FirefoxでURLの右側にある検索窓に何か打ち込んでいると候補が出てくるアレです。 こんにちでは、いろいろなサイトがsuggestの機能に対応している。代表的なものは以下の通り。

Googlehttp://suggestqueries.google.com/complete/search?client=firefox&hl=ja&qu={searchTerms}
Goohttp://search.goo.ne.jp/suggest.jsp?fm=json&ie={inputEncoding}&MT={searchTerms}
Wikipedia(ja)http://ja.wikipedia.org/w/api.php?action=opensearch&search={searchTerms}
Amazonhttp://completion.amazon.co.jp/search/complete?method=completion&search-alias=aps&mkt=6&q={searchTerms}
Yahoo(JP)http://api.search.yahoo.co.jp/AssistSearchService/V2/webassistSearch?output=fxjson&ei={inputEncoding}&p={searchTerms}
NAVERhttp://ac.naver.jp/ac?r_format=opensearch&q_enc={inputEncoding}&r_enc={outputEncoding}&q={searchTerms}
Yahoo! Transit(駅名)http://transit.map.yahoo.co.jp/suggest/search.php?q={searchTerms}

Yahoo! Transitの駅名はJSONではなくてSpace-separeted textですね。 GoogleやAmazonのAPIにアクセスするようにOpenSearchのXMLファイルを書いてもいいけど、自前で用意するにはどうしたらいいのか。 自分のエントリを形態素解析して単語を抜き出し、検索すればいいんじゃないかな。あんまり高度なアルゴリズムは必要ないだろう。 というわけで、やってみた。 うちのサーバはCentOS5系なので、まずは関連するファイルのインストール。 HTMLのパースに使うlxml、DBの操作に使うSQLObjectはEPELから入る。

gccでtccのようなrun

tccは高速なCコンパイラで、あまりにコンパイルが高速なので、まるでインタプリタのように*.cを起動できるという-runオプションがある。確か、コンパイルしたものをファイルに送らずにメモリ上に置いて、そのままmainのアドレスを呼び出していたような気がする。tccもx86_64に対応してくれて、使いやすくなった。

最近はCPUも速くなったので、gccで一時ファイルを使っても、そう大差ない使用感になるだろう。シェルスクリプトでも書けるけど、Pythonで書いてみた。

  • tgcc.py
  • python tgcc.py program.c othersrc.c – arguments
#! /usr/bin/python

import os,sys
import subprocess
import tempfile

def usage():
    print "Usage: %s files... [-- args...]" %(sys.argv[0])
    exit(0)

def main():
    args=sys.argv[1:]
    try:
        sp=args.index("--")
        files, arg=args[:sp],args[sp+1:]
    except ValueError,detail:
        files=args
        arg=[]
    if len(files)==0:
        usage()
    f=tempfile.mktemp()
    r=subprocess.call(["gcc", "-o", f]+files)
    if r==0:
        r=subprocess.call(["temp"]+arg, executable=f)
    os.unlink(f)
    exit(r)

if __name__=="__main__":
    main()

例えば、

  • echo -e “#include <stdio.h>\nint main(){printf("hello,world\\n");}” > /tmp/hw.c
  • python tgcc.py /tmp/hw.c

で、hello,worldが表示されますね。-Wall等のオプションも有効です。

gcc関係では、もう少しだけ面白い遊びを考えてます。もうかなり長いこと温めていて、まだ温めているってだけですが。

「差分」バックアップの話

バックアップが面倒だなと思った。実際にバックアップなんてあまり真面目にやってない。

無駄に容量食ってない? という疑念が頭を離れないのでやる気がしないのですね。

じゃあ、変更しそうなファイルだけを人間が選択してバックアップ?

それはなんか違う。

dump/restoreの差分の扱いもなんか違うように思う。ワタシは再現するのに必要なファイルだけをバックアップしたい。

rpmのデータベースを見て、変更・追加されたファイルだけをtarにまとめておけばいいじゃん。というわけで少し手を動かして書いてみた。

  • deltadump.py
  • python deltadump.py –f /tmp/root.tar.gz –d /
Usage: deltadump.py [options]

deltadump.py: backup([file for file in allfiles if not in rpm_db])

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -f FILENAME, --file=FILENAME
                        output tar filename(MUST SET)
  -d DIRNAME, --dir=DIRNAME
                        backup directory(MUST SET)
  -X, --cross-filesystem
                        cross filesystem
  -t, --skip-timestamp  skip timestamp
  -s, --skip-socket     skip socket/pipe
  --hash                compare hash value
  --ignore=IGNORE       ignore dir("," separated)
  -D, --skip-new-directory
                        skip new directory

ハッシュ値(SHA-256)の比較は時間がかかるので、デフォルトではサイズしか見ていません。ファイルサイズもハッシュ値もprelinkが邪魔なんですが、prelink -uして元に戻したファイルを比較してます。prelinkは遅いしnekovmなど誤検出してしまうものがあるため、設定ファイルのブラックリストを解析しつつゴニョゴニョと汚くやっているのは皆様の参考になるかどうか…。