Elisp performance tuning

究極の隙間産業 elisp のパフォーマンスチューニングをした。
↓これを作ってるときにスピードが遅い原因を調べたときのメモを適当にまとめた。

http://d.hatena.ne.jp/mhayashi1120/20130827/1377606396

なお aes.el との比較は aes-256-cbc で大雑把に比較した。
入力パラメータは kaesar.el のデフォルトと同じにしておく。
cbc という block mode は openssl コマンドのデフォルトにもなっている、実行コストは高いが安全なモードである。

(setq aes-default-method "CBC")
(setq aes-Nk 8)

活用したライブラリや関数

  • elp ライブラリ (関数呼び出しにかかる時間がわかる)
  • benchmark ライブラリ (GC の回数とかかる時間がわかる)
  • macroexpand-all

elp はあんまり日本語の解説がみられない。
そんなに使い方が難しいわけじゃないのだけど

M-x elp-instrument-package kaesar

一度暗号、復号を実行してから M-x elp-results をすると関数の実行時間一覧が得られる。
以下に記述するような defsubst form の inline 展開をした後だと、それぞれの subst 関数の実行時間が得られないので byte-comiple されてないものを load しておく。
この結果をもって最適化の目安とする。

performance up の指針

  • byte-compile されてない場合は遅くても構わない。
  • AES の仕様を十分に理解できるまで何度も読み続けたいので可読性を犠牲にしない。AES の仕様に書かれた関数と elisp 関数の整合性をできる限り保つ。
  • 関数呼び出しを減らす。具体的には defsubst で byte-compile 後に inline 展開する。
  • lisp ファイルを load する時間は遅くても構わない。もちろん速ければそれに越したことはない。

結果を cache する

できるだけ実行時の計算を抑えるために、演算の結果を前もって load 時に cache する。
例えば乗算は char * char の範囲しかないので 256 の要素を持つ集合を 256 個作る。
集合を保持する形式は何にするか? hash だと明らかにオーバースペックであろう。list か vector か、どちらがいいか。

普通に考えれば

list: nth O(n)
vector: aref O(1)

だと思うけど、一応 benchmark してみる。先頭の要素と最後尾の要素を参照する際の消費時間を測る。

(let ((v (make-vector 255 0)))
  (list
   (benchmark 1000 `(aref v 0))
   (benchmark 1000 `(aref v 254))))

=> ("Elapsed time: 0.000115s" "Elapsed time: 0.000111s")

(let ((l (make-list 255 0)))
  (list
   (benchmark 1000 `(nth 0 l))
   (benchmark 1000 `(nth 254 l))))

=> ("Elapsed time: 0.000151s" "Elapsed time: 0.001107s")

というわけで、先刻の予想通り vector を使うのがよろしい。いくつかの計算結果を vector に格納すると大部はやくなった。数字は残っていない。

ループを減らす

暗号機能内部の話は複雑なのでカット。とにかく内部のループ処理を少し減らした。
aes.el を参考にしたわけじゃないけど、結果としてほとんど同じことをするようになった。*1

ループを減らす (2)

4 byte を一塊として扱っているので 4 回ずつのループが随所にある。
マクロでうまく展開する方法が思いつかなかったので単純に 4 個別々の form に。

(dotimes (i 4)
  (aset word i (aref kaesar--S-box (aref word i))))

↑を単純にこんな感じに。

(aset word 0 (aref kaesar--S-box (aref word 0)))
(aset word 1 (aref kaesar--S-box (aref word 1)))
(aset word 2 (aref kaesar--S-box (aref word 2)))
(aset word 3 (aref kaesar--S-box (aref word 3)))

compile 時に計算をする

あんまり使ったことなかった eval-when-compile, eval-and-compile を使って byte-compile 時に前もって計算をしておく。
byte-compile する時間は長くなるけど load する時間は一気に短縮される。計測はしていない。

defsubst を使う

inline 展開するように defun ではなく defsubst を使った。
defsubst にした関数は呼び出し元の定義よりもソース上部に書いておかないといけない。
load を早くするため、defsubst form を上述した eval-when-compile で包むと内部でしか使わない余計な関数が生成されなくなるようだ。

GC を減らす

GC を減らすための指針。

  • append 関数を使わない。できれば nconc を使う。append 関数を使っていても、それで生成された list を nconc でくっつけるのならセルの無駄遣いにならないので ok
  • vconcat, make-vector など配列を生成する関数を実行時にできるだけ使わない。
  • substring で文字列切り出しもしない。これは配列を生成してるのとほとんど同じ(はず)。
loop マクロ

この暗号プログラム作ったときに初めて cl ライブラリをふんだんに使ってみた。loop マクロが超便利。
しかし、こういう macro 展開をしているのに気付かなくて悩んでた。
append 関数がすごくセルを無駄遣いして GC の原因になる、ということを知った後に macroexpand をしてみるとすぐに分かった。


どちらも同じものを生成するのかと思ったら、

(loop for x in '(1 2 3)
      append (list x))

(loop for x in '(1 2 3)
      append (list x)
      into res
      finally return res)
(macroexpand-all
 '(loop for x in '(1 2 3) append (list x)))

=>

 (progn
   (let* ((--cl-var-- (quote (1 2 3))) (x nil) (--cl-var-- nil))
     (while (consp --cl-var--)
       (setq x (car --cl-var--))
       (setq --cl-var-- (nconc (reverse (list x)) --cl-var--))
       (setq --cl-var-- (cdr --cl-var--)))
     (nreverse --cl-var--)))

append keyword で返却値を設定すると (nconc (reverse (list x)) --cl-var--) で繋いでいってくれるけど、

(macroexpand-all
 '(loop for x in '(1 2 3)
        append (list x)
        into res
        finally return res))

=>

(progn
  (let* ((--cl-var-- (quote (1 2 3))) (x nil) (res nil))
    (while (consp --cl-var--)
      (setq x (car --cl-var--))
      (setq res (append res (list x)))
      (setq --cl-var-- (cdr --cl-var--)))
    res))

append に into を加えるとなぜか (append res (list x)) へと展開される。
なんでこんな仕様なのか考えてみると into で代入先の変数を明示しちゃうと、プログラマに見えるようにしないといけないから reverse して nconc して隠し変数に蓄積するわけにいかないから。

とにかく vector と list を削減

タプルを使いたいときでも list, vector を生成しない。めんどくさくてもそれぞれの item をそれぞれ別々のローカル変数に格納する。

わかりづらい例

(let ((x [0 1 2 3])
      (y [4 5 6 7]))
  (let ((v1 (vector (aref x 1) (aref y 2)))
        (v2 (vector (aref y 0) (aref x 3))))
    (logxor (aref v1 0) (aref v2 1))))

みたいにしてたとこを

(let ((x [0 1 2 3])
      (y [4 5 6 7]))
  (let ((v1-1 (aref x 1))
        (v1-2 (aref y 2))
        (v2-0 (aref y 0))
        (v2-3 (aref x 3)))
    (logxor v1-1 v2-3)))


さしたる意味もなく append してた箇所はがんがん削減。

やってみたけど、そんなに意味なかったこと

loop マクロを while ループへ。loop マクロは遅いなどと思いこんでたけど、 byte-compile したら展開されてほとんど一緒になる。
その他、愚にもつかないことをいっぱいやったような。。あとは思い出せない。

M-x profiler-start

なにこれすごい。一通り書き終わった頃に存在に気付いた。いつからか Emacs に標準装備されたっぽい。
しかし GC で開放されてるメモリが獲得された箇所は特定できないのかな?
まだ十分に理解してないけど、この performance tuning では使う方法が思いつかなかった。

終結

core 関数を byte-compile したもので正確なベンチマークを取ってみた。

aes.el

(load-library "aes.elc")
(benchmark 10
    '(aes-Cipher 
      "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
      '(((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0) ((0 . 0) 0 . 0))
      4 14))

=> "Elapsed time: 0.002048s"

kaesar.el

(load-library "kaesar.el")
(load-library "kaesar.elc")
(byte-compile 'kaesar--cipher!)
(setq kaesar--Nk 8)
(setq kaesar--Nr 14)
(benchmark 10
    '(kaesar--cipher!
      [[0 0 0 0][0 0 0 0][0 0 0 0][0 0 0 0]]
      [[[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]]]
      ))

=> "Elapsed time: 0.001660s"


と、いうわけで最終的には若干 aes.el を上回ることはできたようだ。
数学の知識があればもっとスピードアップできるのだろうか。

配列から配列にまとめてコピーする機能、 C でいうところの memcpy な機能が C レベルで提供してあればもう少し高速化できる気がする。

*1:SubByte しながら ShiftRow する、MixColumn しながら AddRoundKey する

Elisp で AES 暗号

Emacs には標準では暗号用の pure elisp なパッケージがないみたいだし、暗号プログラムってどんなものか知りたかったので elisp で作ってみた。
openssl コマンドの暗号出力と仕様を統一して、出力を突き合わせてテストしているのでバグってはいないのでしょう。

しかし、一通り動くようになってから*1よくよく検索してみると EmacsWiki に aes.el が上がってた。気付かんかった。
気付いた時点で簡単にベンチマークしてみると、僕のバージョンよりもかなり速い。向こうは base64 もしてるのに。。最新バージョンではほぼ同等のスピードになってる。
ただ、あちらのバージョンは、暗号に短いパスワードを設定してあると、違ったパスワードを渡しても復号できてしまうという致命的なバグがあるようです。詳しくは調べていません

openssl コマンドを利用した実装

まずは openssl コマンドを使った実装を作ってみた。
aes に留まらず、des, blowfish など各種暗号が使える。

https://github.com/mhayashi1120/Emacs-openssl-cipher/

いつもはこれで終わりなんだけど、Salted__ なる文字列が何なのか気になりはじめて、暗号のことを調べ始めたら、つい elisp でやってみたくなっちゃった。

Pure Elisp による AES の実装

https://github.com/mhayashi1120/Emacs-kaesar/

byte-compile しないと遅くて使いものにならない。byte-compile することで 4KB ぐらいの小さなデータなら、おそらく大抵の環境で実用程度に動作すると思われる。
僕の環境では、暗号、復号とも 1 秒もかからなかった。
逆に小さい平文であれば外部コマンドを呼び出すコストがないので、上の openssl を使った実装よりも相当に速い。
パスワード、パスフレーズの暗号化や hex や base64エンコードしてある token などの暗号化ぐらいなら実用的な役にも立つはず。

疑似コード写経するだけなら楽だろうと思っていたのだけど、僕の数学の知識は高一レベルなので、知らない数学用語が出てきてかなり大変だった。
ガロア体とか聞いたことなかったし。

(require 'kaesar)
;; 暗号化
(defvar my-secret nil)

(let ((raw-string "ぼくの秘密"))
  (setq my-secret (base64-encode-string (kaesar-encrypt-string raw-string)))
  (clear-string raw-string)
  my-secret)

=> "U2FsdGVkX1+SwQ8QqoY5vxNhnb35C5SyghtyPJhOSAk="

(base64-decode-string my-secret)

とやると、ちゃんと訳のわからないデータになってる。*2
はてなに記事あげるために base64 encode してる。

;; 復号化
(kaesar-decrypt-string (base64-decode-string my-secret))

=> "ぼくの秘密"

Salted__

冒頭で疑問に思った salt は要するに同じ平文と鍵に大して、異なる暗号出力を得るためのもの。
ネットに転がっているサンプルプログラムでは IV (Initialization Vector) をプログラムにハードコーディングしてるものが散見されるけど、あれは IV の意味がなくなってしまうため NG。IV は平文のままで暗号文と一緒に配布してもよいので毎回生成しなおそう。

Openssl の場合は "Salted__" 文字に後続する 8 byte と password を入力として、鍵と IV を生成している。

Rijndael アルゴリズム

AES の実体は rijndael というアルゴリズム
rijndael のアルゴリズムは入力ストリームに対してルービックキューブのような操作をする。
AES の場合は入力を 4 × 4 の正方形に配置してから以下みたいな感じの操作をする。

  • 列をそれぞれ縦にちょっとずらす。
  • 行をそれぞれ横にちょっとずらす。
  • 色を一定のルールで変える。
  • 鍵をかける。(XOR で)

こういった操作を何回も繰り返す。

復号のときはこの逆をするだけ。

ちなみに最後尾のバイトに対するブロック化 (4 × 4 化) は暗号モードによって異なる。
暗号モードによってはどうやら復号に失敗したことをプログラムで検知できない。

melpa

ちょっと melpa って文言をソースに入れてただけなのに purcell さんにあっという捕捉されて mepla に登録されてしまいました。
ほんとはもうしばらくおいといてからにしようと思ってたのですが、まあいいです。
今の様子では download 数が伸びることはありませんね。先行の aes もそんなに伸びてないですし。

*1:もう 2 年前ぐらいかな?

*2:パスワードは書きません。

ゆうちょダイレクトからのメール、SPF が設定されていない(?)のに gmail で pass してしまう

Received-SPF: pass (google.com: best guess record for domain of bea@jp-bank.japanpost.jp designates 202.234.200.135 as permitted sender) client-ip=202.234.200.135;

となってるので

dig -t txt jp-bank.japanpost.jp

とやってみるも txt レコードが返ってこない。

;; QUESTION SECTION:
;jp-bank.japanpost.jp.		IN	TXT

;; AUTHORITY SECTION:
jp-bank.japanpost.jp.	1200	IN	SOA	ns.jp-bank.japanpost.jp. root.jp-bank.japanpost.jp. 2012080601 10800 1800 31536000 86400

SPF なるクエリタイプもあると書いてあったりするので

dig -t spf jp-bank.japanpost.jp
;; QUESTION SECTION:
;jp-bank.japanpost.jp.		IN	SPF

;; AUTHORITY SECTION:
jp-bank.japanpost.jp.	1200	IN	SOA	ns.jp-bank.japanpost.jp. root.jp-bank.japanpost.jp. 2012080601 10800 1800 31536000 86400

やっぱり返ってこない。

もしかして、問合せ元の IP アドレスごとに返ってくる値を変えているとか?

とんちんかんなことやってるのかもしれないけど google public dns にも聞いてみる。

dig @8.8.8.8 jp-bank.japanpost.jp -t txt
;; QUESTION SECTION:
;jp-bank.japanpost.jp.		IN	TXT

;; AUTHORITY SECTION:
jp-bank.japanpost.jp.	1500	IN	SOA	ns.jp-bank.japanpost.jp. root.jp-bank.japanpost.jp. 2012080601 10800 1800 31536000 86400

やっぱ何も返ってこない。


手元に yahoo からのスパムがあったのでこちらの spf を調べてみる。

Subject: TポイントがYahoo! JAPANで使える! 最大5,000ポイントが当たるキャンペーン開催中[Yahoo! JAPAN]
From: Yahoo!ショッピング
Received-SPF: pass (google.com: domain of osusume.********************-mhayashi1120=gmail.com@err.yahoo.co.jp designates 114.111.101.154 as permitted sender) client-ip=114.111.101.154;
dig -t txt err.yahoo.co.jp
;; ANSWER SECTION:
err.yahoo.co.jp.	900	IN	TXT	"v=spf1 include:bulk-spf.yahoo.co.jp ~all"
dig -t txt bulk-spf.yahoo.co.jp
;; ANSWER SECTION:
bulk-spf.yahoo.co.jp.	900	IN	TXT	"v=spf1 include:bulk-spf-east.yahoo.co.jp include:bulk-spf-west.yahoo.co.jp ~all"
dig -t txt bulk-spf-west.yahoo.co.jp
;; ANSWER SECTION:
bulk-spf-west.yahoo.co.jp. 900	IN	TXT	"v=spf1 ip4:124.83.128.0/17 ip4:114.111.64.0/18 ip4:183.79.0.0/16 ~all"

114.111.64.0/18 が 114.111.101.154 にマッチする、ということで正しく spf の設定がされているのだろう。


ゆうちょの方 (jp-bank.japanpost.jp) はなんで pass してしまうんだろう?
んー。dns 以外にも何か調べる要素があるのだろうか。


apt に python-spf っていうツールがあったのでこちらにも try

/usr/share/pyshared/spf.py jp-bank.japanpost.jp

=> None

やっぱ取得できん。

apt の spfquery

spfquery -i 202.234.200.135 -s information@jp-bank.japanpost.jp &

=>

StartError
Context: Failed to query MAIL-FROM
ErrorCode: (2) Could not find a valid SPF record
Error: No DNS data for 'jp-bank.japanpost.jp'.
EndError
none

わからん。ゆうちょからの数日前のメールをふと掘り起こして調べていたのだけど、ここ数日で DNS の設定かえた、なんてあるのかな?

gmailspf 判定が特殊な気がしてきた。

世論調査の電話がかかってきた。

今朝 0120773661 という電話から「北國新聞社」を名乗って世論調査の電話がかかってきた。

http://www.jpnumber.com/freedial/numberinfo_0120_773_661.html

↑これの 7/16 に書いたのがおれなわけだが。

どうやら、数日前から何度もかけてきてたみたいで、家族に「30 代男性」が同居していることを確認していたらしい。
おれはそういう電話がかかってきてたことは、家族に聞いてなくて、自分で電話とるまで知らなかった。

最初は、どうも不審だけど 2,3 分ぐらいならまあいいかと答えていたのだけど、質問に答えているうちに、向こうの電話の後ろで拍手してる音とか騒いでる声とか聞こえてきてものすごく胡散臭く思うようになってきた。

調査のやり方も「1. 自民党 2. 民主党 3. … 11. みどりの風から番号でお答えください」っていう聞き方をやや早口でしてくるのだけど、電話なんて媒体を使って次々に番号と単語の pair 出しても、番号なんていちいち覚えていられないですし。
単語で答えようとすると「番号でお願いできますか」だって。
世論調査ってこんなもんなのだろうか。だいぶ前にも一回あったかな。あの時は機械的な声で質問してきてプッシュ音で選択するタイプだった記憶はある。
そして、最後に所在市町村も聞かれるのだけど、家の電話にかけてるんだから、市町村ぐらいはそっちで特定できてるだろ。
なんだか、いろいろと手際が悪いなーと感じた次第。

最後にそちらは本当に北國新聞社の方なのですか?と聞いてみると、上司の人に電話を変わってくれて、その方が答えてくれたところによると、はぐらかしてなかなか答えなかったのだけど、北國新聞から依頼されたのだとか。
じゃあ、北國新聞社の方ではないのですね、なんという会社ですか?と会社名を聞いてみるも、答えられないです、ということだ。
自分の会社の名前も言えないって、それはおかしいでしょうということは申し上げた。
電話はコンピュータで無作為に抽出した電話番号に適当にかけまくってるとかなんとかで、北國新聞を購読しているかどうかとかは特に関係ないらしい。
30 代男性って属性の解答を求めていた様子とどうも整合性がとれない気もする。

しかし、世論調査についてこんなのを発見した。

http://www.asahi.com/special/08003/rdd.html

 世帯に電話がつながったら、調査の趣旨を説明した後、その世帯に住んでいる有権者の人数を聞きます。コンピューターでサイコロを振る形で、その中から1人を選んで調査の対象者になってもらいます。電話に最初に出た人を対象にすると、在宅率の高い主婦や高齢者の回答が多くなってしまい正確な調査になりません。

 選ばれた人が不在でも、一度決めた対象者は変えず、時間を変えて最大6回まで電話をかけます。また、すぐには応じていただけない場合でも、重ねて協力をお願いしています。これも、回答者の構成を「有権者の縮図」に近づけるためです。

家にかかってきた電話、最初に誰が出たのかわからない。じいちゃんが出てたのなら、もう覚えていないみたいだから、最初にどんなやりとりあったかわからんな。
最初は不審に思わなかったけど、電話の後ろで騒いでる声とかで一気に不審感が盛り上がったのかなーと思う。
しかしながら、疑問や不審点はほぼ解消した。残る不審点はどうしても会社名を名乗らないってとこ。


以上、北國新聞などの地方新聞が大嫌いな男の感じたことでした。

田舎の図書館で プログラミング Clojure をみつけた

カーリルで検索してみると『プログラミング Clojure』が置いてある図書館が近くにあった。
近くで所用があったときに時間が空いて、思い出したので寄ってみた。
周りにある本は Excel, Photoshop, Powerpoint の本ばかり。それも古めの。比較的新しめ、かつ Ohmsha の背表紙ってだけで書架から浮いてる感じがする。
写真撮りたかったけど、撮影していいかわからなかったので撮らなかった。

そして RubyJava の本すら見当たらないのになぜに Clojure ??全然わからん。どうしてこの本がこの図書館で購入されたのかに思いを馳せている。
地方図書館だとプログラム関係の本があんまり置いてないから、じっくり読みたいのは全部買うしかないのですよね。

プログラミングClojure

プログラミングClojure

Clojure からは def, defn という命名から怠惰でなまくらな香りがしてきて、おれ好みである。
スレッド関係のお話も面白そうなので、とりあえず一通り読んでみたい。

レ・ミゼラブルの民衆の歌

映画みてるときにどこかで聞いたことあるようなないような、でもこれ、ラ・マルセイエーズじゃないよなーと思いながら聞いてた。
相当、有名な曲みたいだからきっと聞いたことあったのだろうな。
どこか耳に残った旋律と力強さ、レ・ミゼラブルの重々しいストーリーと重なることで、映画を観た後はちょっと聞いただけで聞き分けられるようになった。

その歌を NHK の歌謡ショーで StarS というグループが日本語で歌っていたのを聞いたので、ちょっと調べてみた。

この歌、いくつもの国の言葉に翻訳されてるらしい。
歌詞は、元々はフランス語だろう。*1フランス語から英語に訳された過程では、訳された単語が落ちてない気がしたのだけど、実はそうでもないらしい。

http://foggykaoru.exblog.jp/19123004

英語版も日本語版も元のとかなり違うみたいだなぁ。個人的には英語版に訳されたのが一番好き。

しかし、これよく読むとフランス語から日本語に訳されたのではなく、英語に一旦訳されたものが日本語に訳されてるように見える。
この辺の歌詞なんかが特に。

When the beating of your heart
Echoes the beating of the drums
There is a life about to start
When tomorrow comes!
鼓動があのドラムと響き合えば
新たに熱い命が始まる
明日が来た時 そうさ明日が

「進歩」とか「畑」などの農民を示す大事な用語は落ちてるのにドラムとか明日とかの用語はしっかり付合してる。
ほぼ間違いないだろ。


↑の動画中で

Who will not be slaves again!

↑のところは強調して歌っていたからはっきり聞き取れたんだけど、これ↓聞くと日本語には訳されていないっぽい。

上の動画は 2012 年のミュージカル映画のプロモーションに作られたみたいだけど、歌詞(訳詞)自体はそれより昔からある伝統的なものみたい。

屍越えて拓け明日のフランス!

でフランスって国の名前も出てるのに「奴隷」って言葉が訳されてないのはどうも不自然に感じる。
言葉の持つ負の意味から語呂がどれだけ悪くても意地でも訳したくなる。
日本語訳されたのはきっとかなり前なのだろうけど、どうしてでしょうね。

http://cafecentral.tea-nifty.com/wien/2005/04/love_f923.html

そもそも、レ・ミゼラブルは小説なのに、この『民衆の歌』ってのはどこで産まれたものなのか。それも調べてたけどいまいちわからない。

http://ja.wikipedia.org/wiki/%E3%83%AC%E3%83%BB%E3%83%9F%E3%82%BC%E3%83%A9%E3%83%96%E3%83%AB_%28%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%B8%E3%82%AB%E3%83%AB%29

これみるとフランスで上映、その後で英語圏で上映して大ヒットしたと思える。どこでこの歌が挿入歌になったのだろう?
ここで岩谷時子さんが訳詞をしてるらしいから、この時点で英語から日本語へ訳されたのかな?
日本公演が 1987 年だからこの頃の訳なら言葉狩りで「奴隷」が省かれたということもなさそう。
蛍の光』の元の歌のことを調べたときも驚いたものだけど、歌詞ってものは訳されるとまったくの別物になっていくのかもしれない。『蛍の光』のはまた別格だったけど。


なんでこんなにしつこく調べてたかというと、戦前の検閲下での訳詞が今も流通してるのならもったいないなーと思ったから。
でも、上の仮説が正しいなら、ただ単に語呂が悪いから省かれただけだな。
slave を「社畜」と絡めて訳してあったら、多くの日本人を煽動できて面白そうだと思ったりもした。
日本語版、やたらと繰り返しが多いのでどこかに入れることはできたんでないかな。

いろいろ調べてるとまた興味が湧いてきた。レ・ミゼラブルは昔読もうとして挫折した記憶があるのだけど、もう一度チャレンジしてみたい。
映画もはやくレンタルでもう一度みたい。それから、アンハサウェイはまじ天使。

レ・ミゼラブル (1) (新潮文庫)

レ・ミゼラブル (1) (新潮文庫)

*1:違うかも。後述する。

Emacs 電子六法 laws.el を fork

日本の法律を http://law.e-gov.go.jp/ からダウンロードして Emacs で表示する laws.el という elisp package があります。

http://www.ne.jp/asahi/alpha/kazu/laws.html

Emacs で開いてインクリメンタルサーチしてコピペできたりするのはうれしいので愛用してるのですが、最近は更新されていません。

GPL なので手元にあるものをそのまま公開してもよかったのですが、念のため作者の方にメールを送ってみたところ、再配布を快諾いただくことができたので、github にて公開します。

https://github.com/mhayashi1120/japanlaw.el/

概ね、以下のような変更をしてあります。

  • laws.el から日本に特化してることを示す japanlaw.el に改名
  • 保存形式の utf-8 対応
  • apel 非依存化
  • いくつかの細かい不具合対応
  • GNU Emacs 24.3 の cl-* 対応
  • 保存ディレクトリを ~/.laws.d → ~/.japanlaw.d に変更

laws -> japanlaw に変わったことさえ覚えておけば、旧バージョン laws.el からは特に意識することなく移行できるかと思います。
(できるだけ短くするために s を外しました。)

追記: MELPA へ登録しました。(2013-04-09)