Google Summer of Code 2011 に採択されました

どうもこんばんは! @laysakura です.

周りの方々からたくさんの助けを賜りながら,この度 Google Summer of Code 2011 に採択される運びとなりました.
とっても嬉しいです^^

今日から大体9月までの間, GCCOpenMP実装をベターなものにしていくプロジェクトに取り組んでいきます.
このblogでも折りにつけて途中経過を報告していくつもりですので,どうぞよろしくお願いします.

おそらく最も情報量が多そうな,Proposalを晒しておきます.
来年以降に挑戦される方のご参考になれば嬉しいです.

Proposalへのリンク

以下,消えたときのためにコピペ
(書式が読みづらいのはご勘弁願います)


===

Email: XXX

Short description: GCC's implementation of OpenMP Task is slower compared to other implementations like that of Intel C Compiler, Cilk, and Nanos4. This project aims to give speed-up to `task' construct in libgomp. This will be done by migrating the implementation of Nanos4. Also, to make reasonable and sophisticated implementation, learning of many `task' implementations is involved in this project.

Additional info: http://gcc.gnu.org/ml/gcc/2011-04/msg00107.html
Proposal

The biggest goal for this project is to add speed-up to OpenMP Task in libgomp, GCC. Currently, the implementation of `task' in libgomp has two problems:

    `tied task' is slower than other implementations.
    See: Evaluating OpenMP 3.0 Run Time Systems on Unbalanced Task Graphs [PDF]
    `untied task' is not implemented.

First, I try to implement faster `tied task' in libgomp.
If this could be successfully done, I would also try to implement `untied task' in libgomp, which has not been implemented in it yet.

There are a lot of possible implementations for OpenMP Task since OpenMP doesn't specify how to deal with task scheduling in detail. For instance, should worker be on every CPU core or exist globally, should tasks be created in breadth-first or depth-first order, should queue be FIFO or LIFO.
Therefore, it is quite important to decide what kind of implementation is the best (at least better than current one in libgomp). So this project involves investigating and testing other existing implementations. NANOS group , which provides OpenMP runtime library called Nanos4, has conducted detailed evaluation of many `task' implementations. My objective during GSoC is to extend the survey presented in Evaluation of OpenMP Task Scheduling Strategies [PDF] in order to implement the different scheduling schemes in libgomp. After that, I will evaluate the GCC implementation of these schedulers over a set of task benchmarks, including the testsuite of libgomp and Barcelona OpenMP Tasks Suite (ref: Barcelona OpenMP Tasks Suite: A Set of Benchmarks Targeting the Exploitation of Task Parallelism in OpenMP [PDF]) to select the appropriate scheduler for libgomp.
About Myself

Name
    XXX
E-mail
    XXX
University
    The University of Tokyo, Japan
Position
    Undergraduate

Open Source Activities

    Would be an Emacs contributor for improving DocViewMode.
    Currently in the process of copyright assignment.
    See the discussion about my work.

Other Activities

    Implemented very simple C Compiler (PyCC)

Surroundings

This project is closely related to my graduation thesis, so I MUST try hard on it.

I have good advisers, not only the members of GCC ML but my teacher and senior associates.

Machine with 32 cores is accessible. It'll help testing so much.

Better libgomp `task' will help many researchers and programmers, including me, so I'm so motivated in this work.
Schedule
Milestone 0: (4/25 - May)

    Evaluate various implementations in Nanos4
    Read articles related to OpenMP Task
    Read the code of Nanos4 and understand the implementation

Milestone 1: week 1 - week 2

    Read the code of libgomp and understand the current implementation
    Specify where to fix in libgomp/

Milestone 2: week 3 - week 4

    Determine what kind of `tied task' implementation to migrate from Nanos4
    Start implementing new task scheduling in libgomp

Milestone 3: week 5

    Continue to implement the new task scheduling in libgomp

Milestone 4: week 6 - week 7

    Evaluate the new implementation

Milestone 5: week 8 (mid-term)

    Submit the new implementation of `tied task' and its evaluation
    Have the mentor examine the evaluation results

Milestone 6: week 9 - week 11
o If already succeeded in implementing `tied task'

    Start implementing `untied task'

o Otherwise

    Fix `tied task' implementation

Milestone 7: week 12
o If already started implementing `untied task'

    Finish the implementation of it
    Evaluation

o Otherwise

    Evaluation of `tied task' implementation

Milestone 13: week 13 (pencil down)

    Write helpful document about the implementation

Ubuntu 11.04で一部コマンド(mv,cp,ls,lessなど)のディレクトリ補完

Ubuntu 11.04 で bash を使うと,一部のコマンドでディレクトリ補完が気持ち悪くなります.
foo/hoge ディレクトリがあるときは,

mv fo[tab]

によって

mv foo/

と補完して欲しいのに,

mv foo/(半角スペース)

と,末尾に一個余分な半角スペースが入るせいで,

mv fo[tab]
-> mv foo/
-> mv foo/ho[tab]
-> mv foo/hoge

というような流れるような補完ができません.

原因は bash-completion の設定にありました.
/etc/bash_completion をエディタで開いて, "mv" とかで検索すると,

for i in a2ps awk bash bc bison cat colordiff cp csplit \
    curl cut date df diff dir du enscript env expand fmt fold gperf gprof \
    grep grub head indent irb ld ldd less ln ls m4 md5sum mkdir mkfifo mknod \
    mv netstat nl nm objcopy objdump od paste patch pr ptx readelf rm rmdir \
    sed seq sha{,1,224,256,384,512}sum shar sort split strip tac tail tee \
    texindex touch tr uname unexpand uniq units vdir wc wget who; do
    have $i && complete -F _longopt -o default $i
done

みたいな for 文が見つかると思います.
ざっと見た感じ,ここに列挙されているコマンドは,他のコマンドとは別の補完機能を使うようにしているようです.
この「別の補完機能」が(個人的には)気持ち悪いんですね.

というわけで,ディレクトリ補完を気持よくしたいコマンドを for 文から取り除きます.
自分は,ディレクトリ引数を取りそうなよく使うコマンド, awk,cat,cp,df,diff,du,env,gperf,gprof,grep,head,less,ln,ls,mkdir,mv,objdump,patch,rm,rmdir,sort,tail,touch,uniq,wc,wget
を除外しました.

for i in a2ps bash bc bison colordiff csplit \
    curl cut date dir du enscript expand fmt fold \
    grub indent irb ld ldd m4 md5sum mkfifo mknod \
    netstat nl nm objcopy od paste pr ptx \
    seq sha{,1,224,256,384,512}sum shar split strip tac tee \
    texindex tr uname unexpand units vdir who; do
    have $i && complete -F _longopt -o default $i
done

これで気持ちのよい補完生活が戻りました.

2011/09/29 追記

上記の記事では消極的に使用しそうなコマンドだけ取り除いていましたが,
該当部分を全てコメントアウトでも問題ないようです.
現在は,

# makeinfo and texi2dvi are defined elsewhere.
#for i in a2ps awk bash bc bison cat colordiff cp csplit \
#    curl cut date df diff dir du enscript env expand fmt fold gperf gprof \
#    grep grub head indent irb ld ldd less ln ls m4 md5sum mkdir mkfifo mknod \
#    mv netstat nl nm objcopy objdump od paste patch pr ptx readelf rm rmdir \
#    sed seq sha{,1,224,256,384,512}sum shar sort split strip tac tail tee \
#    texindex touch tr uname unexpand uniq units vdir wc wget who; do
#    have $i && complete -F _longopt -o default $i
#done
#unset i

という風にコメントアウトして使っています.

また,http://d.hatena.ne.jp/TrinityT/20110702/1309509133 のようなよりお手軽な方法もあるようです.

OpenMPなコンパイラ,MercuriumとNanos4の導入

前書き

どーもー! @laysakura です!
世の中には色んな Cコンパイラがありますね.かくいう自分も, Borland C Compiler から入り, gcc に挫折し, Visual C++ の cl にお世話になり,今は大抵 gcc を使っています.
そんな数あるコンパイラの中に, Mercurium というものがあります.
主に研究目的用のものらしいのですが,今後個人的にお世話になりそうなので記事にしますね.
公式のドキュメントや関連論文がいかにも研究目的っぽく,最初の取っかかりになりそうな文書がなさそうなので,お役に立てれば幸いです.
(※ Mercurium 単体のインストール法については丁寧な解説があるのですが,後述の Nanos4 を入れないと結局使えないのに, Nanos4 については評価論文くらいしかなかったりします)

Mercurium の概要

Mercurium は,The NANOS group が公開しているCコンパイラです.
しかしただのコンパイラではありません. Source-to-source コンパイラなのです.
gcc などの,いわゆる「ふつうの」コンパイラは,Cのソースファイルを入力とし,オブジェクトコード(*.o)や実行可能形式のコードを出力しますね.
それに対して,Mercurium はCのソースファイルを入力すると,Cのソースファイルを出力します.

別にアホの子な訳ではなく,Introduction to Mercurium (PDF) によると,

  • オブジェクトコードを吐くために,CPUアーキテクチャに固有なコードをたくさん書くのは嫌
  • もう他のコンパイラが優れた最適化を提供してるんだから,最適化は既存のコンパイラをバックエンドとして使うほうがいいじゃん

というお考えらしいです.

じゃあ Mercurium の何が嬉しいんだと言いますと,一つには OpenMP サポートがしっかりしている ということが挙げられると思います(というか自分の必要とするのがこの部分だけなのでそれ以外は知りません).
実は,gccの提供する OpenMP のランタイム libgomp は,他のコンパイラ(Intel C Compiler, Cilk, IBM XL Compiler など)に比べて非常に性能が出づらいようです.
特に, OpenMP 3.0 から登場した `task' 構文に関しては,他のコンパイラ達に大敗を喫しています.
(参考: Evaluating OpenMP 3.0 Run Time Systems on Unbalanced Task Graphs (PDF))

そこで,The NANOS group の提供する Nanos4 を libgomp の代わりに OpenMP ランタイムとして用いて, Mercurium+Nanos4 でコンパイルしてできたCのソースを,バックエンドの他のコンパイラ(gccなど)に食わせてやって,グレートに並列化された実行ファイルを得るわけです.
ここから, Mercurium コンパイラと Nanos4 ランタイムライブラリの導入について書いていきます.

Mercurium と Nanos4 の導入

Mercurium の導入

これに関しては,ここ にしっかりした解説がありますので,こちらを読んでください.
自分は, git を使って次のように導入しました. (git を使う場合は こちら の解説も参照してください)

git clone http://pm.bsc.es/git/mcxx.git
cd mcxx
autoreconf -v -i
./configure --prefix=/home/sho/software/mercurium --enable-tl-openmp-nanos4 --with-nanos4=/home/sho/software/mercurium
make   (もし失敗したら下を読んでください)
make install

makeの際, gcc 4.7.0 だとコンパイルエラーになりましたので,うるさいエラーを警告に変えます.
エディタで src/tl/Makefile を開き, CXXFLAGS の行に -fpermissive を追加します.

CXXFLAGS = -g -O2 -fpermissive

さて,とりあえずここまでで Mercurium コンパイラが手に入りました.この場合, /home/sho/software/bin に mcc という実行可能ファイルが入っています.これがコンパイラ本体です.
PATH を通さないと面倒なので,何とか通しましょう.自分は ~/.bashrc をこんな感じにしました.

export PATH=~/software/mercurium/bin:$PATH
export LD_LIBRARY_PATH=~/software/mercurium/lib:~/software/mercurium/lib/mcxx:$LD_LIBRARY_PATH

ついでにインストールされたライブラリ(OpenMPのランタイムではない)にもpathを通してます.やりましょう.

これで晴れて Mercurium が使えるようになったので,早速試してみましょう!

mcc any_c_file.c
-> cc1: 致命的なエラー: nth_mcc.h: No such file or directory

・・・自分はこれで涙目になりました

Nanos4 の導入

何が起きたのでしょうか?
`nth_mcc.h' というのは Nanos4 の中にあるインクルードファイルです.
Mercurium をインストールする際,

./configure --prefix=/home/sho/software/mercurium --enable-tl-openmp-nanos4 --with-nanos4=/home/sho/software/mercurium

という風に,「Nanos4を使うぜ!」って宣言しましたよね? だったら Nanos4 は当然入れてろよというわけみたいです.

じゃあ Nanos4 入れるかってことになるのですが,これも中々ひどくて 公式のダウンロードリンク がリンク切れ なんですね.ワロス
URL削って ここ を見つけました.ここにある最新版の nano-threads-library-.tar.bz2 をダウンロードしましょう.
あとはディレクトリ内の INSTALL をしっかり読んで,こんな感じに導入します.

tar xvf nano-threads-library-</span>.tar.bz2
cd nano-threads-library-*
./configure --prefix=/home/sho/software/mercurium CC=gcc CFLAGS=-O2
make
make install

Nanos4 をゲットしました!
ランタイムライブラリは,先程既にpathを通した ~/software/mercurium/lib に入ります.
新たに出来たインクルードファイル達にもpathを通すのを忘れずに.
自分は ~/.bashrc に

export INCLUDE=~/software/mercurium/include:$INCLUDE

を追加しました.

テスト

うまく導入できたかテストしてみましょう.
まずは Hello World ですね.
hello.c をこんな感じで作ります.

#include <stdio.h>

int main()
{
  printf("Hello, Mercurium!\n");
  return 0;
}

コンパイルして実行してみましょう.

mcc hello.c
./a.out
-> Hello, Mercurium!

うまくいってるみたいですね!
では, Hello WorldOpenMP版を試してみましょう.
omp.c をこんな感じで作ります.

#include <stdio.h>
#include <omp.h>

int main()
{
#pragma omp parallel
  {
    printf("Hello, Mercurium!\n");
  }
  return 0;
}

まずは gcc で試してみましょうか.

gcc -fopenmp omp.c -o omp-gcc
export OMP_NUM_THREADS=4   # OpenMPのスレッド数を決める環境変数
./omp-gcc
->
Hello, Mercurium!
Hello, Mercurium!
Hello, Mercurium!
Hello, Mercurium!

では, mcc はどうでしょう?

mcc omp.c -o omp-mcc       # Mercurium は OpenMP使うのが当たり前(?)なので, -fopenmp 不要
export OMP_NUM_THREADS=4   # OpenMPのスレッド数を決める環境変数
./omp-mcc
->
Hello, Mercurium!
Hello, Mercurium!
Hello, Mercurium!
Hello, Mercurium!

やりました! ばんざーい!!

導入が成功しているか最終検証

でも,本当に Nanos4 のランタイムが動いているのでしょうか? 実は gcc の libgomp 使ってるんじゃないの?
当然持つべき疑問ですね.調べてみましょう.
まず, omp-gcc が libgomp を参照していることを strace で確かめます.

strace ./omp-gcc 2>&1 |grep 'libgomp'
->

(libgompがないディレクトリを探索して失敗)
...
open("/usr/local/lib/libgomp.so.1", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320*\0\0004\0\0\0"..., 512) = 512

omp-gcc では libgomp を使っているみたいです. omp-mcc ではどうでしょう?

strace ./omp-mcc 2>&1 |grep 'libgomp'
-> (出力なし)

はい.使ってませんね.では omp-mcc が参照しているはずの Nanos4 ランタイムの実体もついでに確かめましょう.

strace ./omp-mcc
->
(たくさんの出力を目grep)
open("/home/sho/software/mercurium/lib/libnthreads-pfm-4.2.0.so", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260G\0\0004\0\0\0"..., 512) = 512

ありました! libnthreads-pfm というのを使っているみたいですね.
それでは最後に完全な安心感を得るとしましょう.

strace ./omp-gcc 2>&1 |grep 'libnthreads-pfm'
-> (出力なし)

これで今夜もぐっすりですね!!

最後に

なかなか丁寧な導入が書けたかと思いますが,いかがでしたか?
研究目的で Mercurium+Nanos4を使う際は, NANOS project の提供しているテストケース Barcelona OpenMP Task Suite (通称 BOTs) が約に立つと思われます.関連論文でも評価はこのテストケースを使って行われています.
快適なOpenMPライフをお楽しみくださいね!

DocViewModeにpatchを送りEmacs contributorになるまで

前書き

やっほ! [http://twitter.com/laysakura[@laysakura]] です!
http://d.hatena.ne.jp/laysakura/20110326/1301168759 の記事にも書いたのですが,最近は Emacs の DocViewMode の patch 書きに腐心していました.
それで色々ありまして,Emacs の contributor に名を連ねることが出来そうなので,それを記事にしました.

自分自身,オープンソースな世界に初めて足を踏み入れて戸惑った&勘違いしたことが多々ありますので,僕の記事を読んで少しでもオープンソースに関わる方が増えれば幸いです.

※文中にソースコードが出てきますが,日本語だけ読んでも大丈夫です.

DocViewMode を少しご紹介

DocViewMode は,Emacs23 以降に標準で入っている機能です.
C-x C-f で PDF や PS ファイルを開くと,バックグラウンドで PNG に変換されてそれが Emacs のバッファに表示されるという素敵なやつです.

事の始まり

WYSIWYG-TeX.el (<-オススメ) を作っている際に,DocViewMode に追加機能が欲しくなりました.
表示している画像を,ウィンドウサイズに合わせて拡大・縮小できるようにしたかったのです.
それを実現するべく,とりあえず自分の環境で動くコードを書きました.

こんな感じです(読まなくてもおkです)

;;; Fit-to-page extension for DocViewMode

(require 'doc-view)

(defconst doc-view-tmp-png-path "/tmp/doc-view-tmp.png")
(defconst doc-view-tmp-pdf/ps-path "/tmp/doc-view-tmp-img")

(defun doc-view-get-dpi ()
  "Returns resolution of your display in Dpi.
If you don't use X Window System, this function asks you Dpi."
  (let ((resolution-info (shell-command-to-string
                          "xdpyinfo |grep 'resolution'")))
    (if (string-match "\\([0-9]+\\)x" resolution-info)
        (string-to-number (match-string 1 resolution-info))
      (read-number "Input your display resolution (Dpi): "))))
;; (doc-view-get-dpi)

(defun doc-view-pdf/ps-size-in-px (pdf/ps-path)
  "Returns (width . height) of PDF/PS file with 'pdf/ps-path`"
  (call-process "gs" nil nil t
                "-dSAFER" "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
                "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET"
                "-dFirstPage=1" "-dLastPage=1" (concat "-sOutputFile=" doc-view-tmp-png-path)
                pdf/ps-path)
  (cons (string-to-number
         (shell-command-to-string
          (concat "echo -n `identify -format \"%w\" " doc-view-tmp-png-path "`")))
        (string-to-number
         (shell-command-to-string
          (concat "echo -n `identify -format \"%h\" " doc-view-tmp-png-path "`")))))
;; (doc-view-pdf/ps-size-in-px "sample.ps")
;; (doc-view-pdf/ps-size-in-px "sample.pdf")

(defun doc-view-window-size-in-px ()
  "Returns current window size in pixel like (width . height) ."
  (cons (* (window-width) (frame-char-width))
        (* (window-height) (frame-char-height))))
;; (doc-view-window-size-in-px)

(defun doc-view-px-to-natural (px dpi)
  "Convert pixel to \"Natural size\", which is about the same size with one printed on paper.
Emacs displays images with \"Natural size\", so this conversion is necessary."
  (* px 1.3871979865e-2 dpi))
;; (doc-view-px-to-natural 596 96)

(defun doc-view-percentage-to-fit (pdf/ps-path dpi)
  "Returns percentage you can set to fit image onto a window.
For example, (88 . 74) means if you set 88 to 'doc-view-resolution',
the width of the image fits the window."
  (let* ((pdf/ps-size (doc-view-pdf/ps-size-in-px pdf/ps-path))
         (pdf/ps-width (car pdf/ps-size))
         (pdf/ps-height (cdr pdf/ps-size))
         (window-size (doc-view-window-size-in-px))
         (window-width (car window-size))
         (window-height (cdr window-size)))
    (cons (* 100.0 (/ window-width (doc-view-px-to-natural (float pdf/ps-width) dpi)))
          (* 100.0 (/ window-height (doc-view-px-to-natural (float pdf/ps-height) dpi))))))
;; (doc-view-percentage-to-fit "sample.ps" (doc-view-get-dpi))
;; (doc-view-percentage-to-fit "sample.pdf" (doc-view-get-dpi))

(defun doc-view-change-size (size)
  "Changes the size of the image viewed in DocViewMode into 'size'."
  (set (make-local-variable 'doc-view-resolution) size)
  (doc-view-reconvert-doc))

(defun doc-view-fit-height ()
  "Makes the height of image fit with window."
  (interactive)
  (doc-view-change-size
   (round (cdr (doc-view-percentage-to-fit (buffer-file-name) (doc-view-get-dpi))))))
(defun doc-view-fit-width ()
  "Makes the height of image fit with window."
  (interactive)
  (doc-view-change-size
   (round (car (doc-view-percentage-to-fit (buffer-file-name) (doc-view-get-dpi))))))
(defun doc-view-fit-page ()
  "Makes image fit with window."
  (interactive)
  (let ((percentage (doc-view-percentage-to-fit (buffer-file-name) (doc-view-get-dpi))))
    (doc-view-change-size (round (min (car percentage)
                             (cdr percentage))))))

(provide 'doc-view-fit-page)

これを提案内容の説明と共に,作者の方にメールで送りました.
もちろん, 文面に失礼がないように・しっかりと伝えたいことが伝わるように,何回か見直しました .

作者ツン期

オープンソースでコードを書くときに心掛けること

実は送った段階では華麗にスルーされるだろうなと8割方思っていたのですが,24時間以内にお返事が返ってきました.
お返事の内容の技術的な部分を要約しますと,

アイディアはいいね.
でもこのコードは不完全だ.採用できないよ.
第一に, 環境に依存するコードを書きすぎている
第二に, 折角こちらが用意した便利な 再描画用の 関数を使用していない

ということでした.

環境に依存するコードを書きすぎている の方は完全に盲点でした.
普段は 自分の Linux 上で 予定通り動いたらOKとしていましたが,知らず知らずのうちに他の環境に対する配慮を欠いていた訳です.
裏でシェルを走らせるような処理を直接書く のは,本当にそれでポータビリティが失われていないかを重々検討してからじゃないといけないと認識を改めました.

既にあるコードを有効利用できていない という指摘も非常に的を射ていました.
「こうすればできるだろう」という処理が頭に浮かんでからはそれをコードに落とし込むことばかりを考え, 既にあるコードでも同じ事をしていないか を確認せず, 同じコードを2度書くな という大原則を破ってしまっていました.
もちろん自分の書いた範囲では無駄なコードは書いていないはずなのですが,後にソースを読んだところ,やはり 作者のコードには自分の書いたことが,よりベターに書かれていました

オープンソースプロジェクトに patch を送る手順

また,作者にはプロジェクトへ patch を送る方法を丁寧に教えていただきました.
今回は GNU Emacs の部分プロジェクトですので,それに固有な話も多少はあると思いますが,根本的にやることはどのプロジェクトでも同じはずですので参考にしてください.

sudo apt-get install bzr
bzr whoami "Sho Nakatani <xxx@yyy.com>"
cd ~/src
bzr init-repo emacs/
cd emacs
bzr branch http://bzr.savannah.gnu.org/r/emacs/trunk trunk
cd trunk
echo "public_branch = http://bzr.savannah.gnu.org/r/emacs/trunk"; >> .bzr/branch/branch.conf
bzr bind http://bzr.savannah.gnu.org/r/emacs/trunk
  • 最新のテスト環境を得るために,ソースをビルドする
    • 初めは今まで通り Emacs23 を使ってコードを書いていたのですが,意味不明なバグにやられました.原因は, doc-view.el が使用しているある関数が最新の Emacs にしか入っていないことでした.このように時間を浪費しないためにも,ちゃんと最新のソースをビルドして,その環境でテストをしましょう.
./autogen.sh
./configure  (失敗)
sudo apt-get isntall libgif-dev
./configure
bzr pull
make bootstrap
  • 自分のやりたいことに関わるコードはしっかり読む
    • 重要です.これをしなければ,別の人が書いた処理を再び別の場所に書く事になりかねません.そればかりか, 自分が思いつきで書く処理よりも上手な処理がもう書かれている ということが十分あり得ます.作者の方が時間を掛けているでしょうから当然ですね.
  • オリジナルを残しつつ,お試し用のファイルで ソースを書き換えていく
    • patch を送るとは,要するにオリジナルと改変後の diff 結果を送るということです. オリジナルをそのまま残し,お試し用のコピーファイルでガシガシ変更を加えていき,お試し用のが完成したら追加・修正した部分を注意深くオリジナルに反映させていく という手順を踏むのが良いと思われます.もちろんこの手順はバージョン管理システムを駆使して行うこともできます.
  • オリジナルに修正を反映させる
    • この際に,以下の点に最低限注意します.
      • diff 結果に余計なものが表示されない様にする.改行・スペース・タブなども勝手に追加・削除してはいけません.
      • コード全体が取っているコーディング規約を感じ取り,遵守するように努める.
  • patch の作成
    • patch は要するに diff 結果だと述べましたが,それだけでなく,
patch -p1 [オリジナルファイル] < [パッチファイル]

の1コマンドによって [オリジナルファイル] が自分の書き換えたとおりに再現できます.
bzr の場合は,以下のコマンドで patch を作成します.

bzr diff -p1 > fit-to-window.patch

もちろん確認も忘れずに!

patch -p1 lisp/doc-view.el < fit-to-window.path

再実装

作者のメールから得た反省点を活かすように意識しつつコードを書き直し,完成したパッチを送りました.
これがパッチの内容です.

=== modified file 'lisp/doc-view.el'
--- old/lisp/doc-view.el	2011-01-25 04:08:28 +0000
+++ new/lisp/doc-view.el	2011-03-28 08:59:52 +0000
@@ -327,6 +327,10 @@
     ;; Zoom in/out.
     (define-key map "+"               'doc-view-enlarge)
     (define-key map "-"               'doc-view-shrink)
+    ;; Fit the image to the window
+    (define-key map "W"               'doc-view-fit-width-to-window)
+    (define-key map "H"               'doc-view-fit-height-to-window)
+    (define-key map "P"               'doc-view-fit-page-to-window)
     ;; Killing the buffer (and the process)
     (define-key map (kbd "k")         'doc-view-kill-proc-and-buffer)
     (define-key map (kbd "K")         'doc-view-kill-proc)
@@ -665,6 +669,45 @@
   (interactive (list doc-view-shrink-factor))
   (doc-view-enlarge (/ 1.0 factor)))
 
+(defun doc-view-fit-width-to-window ()
+  "Fit the image width to the window width."
+  (interactive)
+  (let ((img-width (car (image-display-size
+                         (image-get-display-property))))
+        (win-width (- (nth 2 (window-inside-edges))
+                      (nth 0 (window-inside-edges)))))
+    (doc-view-enlarge (/ win-width img-width))))
+
+(defun doc-view-fit-height-to-window ()
+  "Fit the image height to the window width."
+  (interactive)
+  (let ((img-height (cdr (image-display-size
+                          (image-get-display-property))))
+        (win-height (- (nth 3 (window-inside-edges))
+                       (nth 1 (window-inside-edges)))))
+    ;; When users call 'doc-view-fit-height-to-window',
+    ;; they might want to go to next page by typing SPC
+    ;; ONLY once. So I used '(- win-height 1)' instead of
+    ;; 'win-height'
+    (doc-view-enlarge (/ (- win-height 1) img-height))))
+
+(defun doc-view-fit-page-to-window ()
+  "Fit the image to the window.
+More specifically, this function enlarges image by:
+
+min {(window-width / image-width), (window-height / image-height)} times."
+  (interactive)
+  (let ((img-width (car (image-display-size
+                         (image-get-display-property))))
+        (win-width (- (nth 2 (window-inside-edges))
+                      (nth 0 (window-inside-edges))))
+        (img-height (cdr (image-display-size
+                          (image-get-display-property))))
+        (win-height (- (nth 3 (window-inside-edges))
+                       (nth 1 (window-inside-edges)))))
+    (doc-view-enlarge (min (/ win-width img-width)
+                           (/ (- win-height 1) img-height)))))
+
 (defun doc-view-reconvert-doc ()
   "Reconvert the current document.
 Should be invoked when the cached images aren't up-to-date."

一番最初に作者に送った提案に比べて,変更点が非常に短くなっていることが分かるかと思います.
しかもそのくせ,元の提案版よりも余程良い動作をするようになりました.
ソースをしっかり読めば読むほど,変更箇所は少なくできる と感じました.

作者デレ期

上記パッチを作者にお送りしたところ,一転してデレてきました.

素晴らしいパッチだ!
テストもちゃんと通ったよ. emacs-dev のメーリングリストに君のパッチを報告しておくよ!
それじゃあ,まず http://www.gnu.org/licenses/why-assign.html を読んで, FSFに著作権を委譲することに同意 してね.
同意できたら, http://git.savannah.gnu.org/cgit/gnulib.git/tree/doc/Copyright/request-assign.future のフォームを埋めて, assign@gnu.org に送ってね.
それで FSF から君に確認が来たら,正式に patch を反映したソースを push するよ.
そしたら君も晴れて Emacs contributor だ!

嬉しす

フォームの埋め方が非常に不安だったので, 作者さんに添削をお願いしました . 案の定, 数カ所問題点を見つけていただきました

今はフォームを送り,FSF からの連絡待ちです.

後書き

大体こんな感じでこの2,3日は過ぎていきました.
何しろ初めてのことばかりで,神経を使う場面もありましたが,非常に得難い経験ができたと思います.

何より, コードを書くという作業が,ソーシャライズされることで何倍も刺激的になる ということを肌で感じられたのが一番の収穫です.

今は emacs-dev ML に作者が patch を投稿してくれた結果,新たに出てきた要望を実装しようとしているところですが,これも楽しみながらやれそうです.

長くなりましたが,この辺でこの記事を終えたいと思います.
Be Social !!

DocViewMode で画像がバッファのサイズにフィットするように拡張

皆様, DocViewMode 使ってますか?
Emacs で PDF やら PS ファイルが見れちゃう便利 Elisp ですね.
Emacs 23 なら普通に \C-x\C-f (find-file) で開くだけで起動しちゃいます.

レポート作成を WYSIWYG-TeX.el (使ってね!!!) でしている時とかにもの凄くお世話になるモードなんですが,残念ながらページに画像をフィットさせる機能がありません...


なければ作ればいいじゃん!

ってなことで作ってみました.
名づけて Fit-to-page extension for DocViewMode !! いえい!!!

次期バージョンアップの際の提案として,勝手に EmacsWiki に書いたり作者様にメールをお送りしたりしましたが,ここでもコードと使い方を掲載しておきます.

  1. 以下のコードを .emacs に追加
;;; Fit-to-page extension for DocView-mode
(require 'doc-view)

(defconst doc-view-tmp-png-path "/tmp/doc-view-tmp.png")
(defconst doc-view-tmp-pdf/ps-path "/tmp/doc-view-tmp-img")

(defun doc-view-get-dpi ()
  "Returns resolution of your display in Dpi.
If you don't use X Window System, this function asks you Dpi."
  (let ((resolution-info (shell-command-to-string
                          "xdpyinfo |grep 'resolution'")))
    (if (string-match "\\([0-9]+\\)x" resolution-info)
        (string-to-number (match-string 1 resolution-info))
      (read-number "Input your display resolution (Dpi): "))))

(defun doc-view-pdf/ps-size-in-px (pdf/ps-path)
  "Returns (width . height) of PDF/PS file with 'pdf/ps-path`"
  (call-process "gs" nil nil t
                "-dSAFER" "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
                "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET"
                "-dFirstPage=1" "-dLastPage=1" (concat "-sOutputFile=" doc-view-tmp-png-path)
                pdf/ps-path)
  (cons (string-to-number
         (shell-command-to-string
          (concat "echo -n `identify -format \"%w\" " doc-view-tmp-png-path "`")))
        (string-to-number
         (shell-command-to-string
          (concat "echo -n `identify -format \"%h\" " doc-view-tmp-png-path "`")))))

(defun doc-view-window-size-in-px ()
  "Returns current window size in pixel like (width . height) ."
  (cons (* (window-width) (frame-char-width))
        (* (window-height) (frame-char-height))))

(defun doc-view-px-to-natural (px dpi)
  "Convert pixel to \"Natural size\", which is about the same size with one printed on paper.
Emacs displays images with \"Natural size\", so this conversion is necessary."
  (* px 1.3871979865e-2 dpi))

(defun doc-view-percentage-to-fit (pdf/ps-path dpi)
  "Returns percentage you can set to fit image onto a window.
For example, (88 . 74) means if you set 88 to 'doc-view-resolution',
the width of the image fits the window."
  (let* ((pdf/ps-size (doc-view-pdf/ps-size-in-px pdf/ps-path))
         (pdf/ps-width (car pdf/ps-size))
         (pdf/ps-height (cdr pdf/ps-size))
         (window-size (doc-view-window-size-in-px))
         (window-width (car window-size))
         (window-height (cdr window-size)))
    (cons (* 100.0 (/ window-width (doc-view-px-to-natural (float pdf/ps-width) dpi)))
          (* 100.0 (/ window-height (doc-view-px-to-natural (float pdf/ps-height) dpi))))))

(defun doc-view-change-size (size)
  "Changes the size of the image viewed in DocViewMode into 'size'."
  (set (make-local-variable 'doc-view-resolution) size)
  (doc-view-reconvert-doc))

(defun doc-view-fit-height ()
  "Makes the height of image fit with window."
  (interactive)
  (doc-view-change-size
   (round (cdr (doc-view-percentage-to-fit (buffer-file-name) (doc-view-get-dpi))))))
(defun doc-view-fit-width ()
  "Makes the height of image fit with window."
  (interactive)
  (doc-view-change-size
   (round (car (doc-view-percentage-to-fit (buffer-file-name) (doc-view-get-dpi))))))
(defun doc-view-fit-page ()
  "Makes image fit with window."
  (interactive)
  (let ((percentage (doc-view-percentage-to-fit (buffer-file-name) (doc-view-get-dpi))))
    (doc-view-change-size (round (min (car percentage)
                             (cdr percentage))))))


;; キーマップはお好きにカスタマイズしてください.
(add-hook 'doc-view-mode-hook
          '(lambda ()
             (local-set-key "f" 'doc-view-fit-page)
             (local-set-key "w" 'doc-view-fit-width)
             (local-set-key "h" 'doc-view-fit-height)))
  1. Emacs を再起動
  1. PDF か PS ファイルを開く (DocView-mode になっていることを確認)
  1. ‘f’, ‘w’, ‘h’ キーを押して,どんどんフィットさせてください


ご意見・ご感想・バグ報告など @laysakura までお願いします!!


追記:
今後は github に情報を載せていきます

編集:
うまくいけば GNU Emacs にコミットできそうなので,著作権表示をとりあえず消しました
ライセンスとかわからんほい^o^