神奈川
ふと、Hercules を試してみる。
S/390 な Debian を入れ、さて 16進浮動小数点を初体験、と思いきや、そうならない。
検索してみると、GNU/Linux では IEEE754 が基本らしい。ちっ
An Assembler Programmer's view of Linux for S/390 and zSeries
他の OS でないと面白くないか。
Wikipedia によれば、法的な問題無しに OS/360, DOS, DOS/VS, MVS, VM/CMS, TSS/370 を動かせるということだが...
<URL:http://www.daionet.gr.jp/~knok/diary/?200604a&to=200604031#200604031> で、「トランザクション処理 - 概念と技法」という本が気になった。
探してみると、訳者の一人による紹介のページが見つかった。 <URL:http://db-www.aist-nara.ac.jp/~miyazaki/TP/index.html>
また、著者らによるセミナーの資料も見つかった。 <URL:http://research.microsoft.com/~gray/WICS_99_TP/>
Hercules (上の Debian GNU/Linux sarge) で、64bit 環境を作ってみた。
まず、コンパイラ回りでそれっぽいパッケージ (lib64gcc1 とか) を入れると、gcc -m64 でバイナリを作れるようになった。ただ、実行できない。s390 というカーネルでは 64bit はサポートしていないらしい。(ちなみに、s390 というのは 31bit らしい)
64bit なやつは s390x というものらしいので入れてみる。すると boot に失敗するようになったが、Hercules の設定で ARCHMODE を z/Arch にして解決。(もともと ESA/390 になっていた)
ついでに、NUMCPU を 2 にして、dual processor にしてみた。これはまったく問題なし。/proc/cpuinfo でも 2つ出てくる。この kernel-image ははじめから smp 対応なのだろう。
wikipedia にも利点としてあげられていたが、たしかに、32bit single processor なマシン上で、64bit multi processor な環境をお手軽に試せるというのは (ときには) 役に立つかも。まぁ、速いわけではないが。
唐突であるが、diff3 を読んでみよう。
diffutils-2.8.1/src/diff3.c:393-395
393: thread1 = process_diff (file[rev_mapping[FILE1]], commonname, &last_block); 394: thread0 = process_diff (file[rev_mapping[FILE0]], commonname, &last_block); 395: diff3 = make_3way_diff (thread0, thread1);
main 関数の中にこんな記述がある。どうも、diff を 2回やって、その結果から最終結果を求めているらしい。
diffutils-2.8.1/src/diff3.c:957
957: diff_limit = read_diff (filea, fileb, &diff_contents);
process_diff は read_diff を呼び出している。
diffutils-2.8.1/src/diff3.c:1182
1182: execvp (diff_program, (char **) argv);
read_diff はなんと execvp している。そーか、外部コマンドを使うのか。せっかく diffutils に入ってるんだから diff 自体をライブラリ化すればいいのに、と思わないでもない。
でも、--diff-program=PROGRAM というオプションで変更できるようだから、外部コマンドを使うコード自体は必要か。
でもでも、そんなオプション誰が使うんだ、という気もする。
試してみると、ちゃんと PATH を見ている。
% strace -f -e execve diff3 a b c ... [pid 13708] execve("/home/akr/bin/diff", ["diff", "--horizon-lines=100", "--", "b", "c"], [/* 37 vars */]) = -1 ENOENT (No such file or directory) [pid 13708] execve("/usr/local/bin/diff", ["diff", "--horizon-lines=100", "--", "b", "c"], [/* 37 vars */]) = -1 ENOENT (No such file or directory) [pid 13708] execve("/usr/local/sbin/diff", ["diff", "--horizon-lines=100", "--", "b", "c"], [/* 37 vars */]) = -1 ENOENT (No such file or directory) [pid 13708] execve("/usr/X11R6/bin/diff", ["diff", "--horizon-lines=100", "--", "b", "c"], [/* 37 vars */]) = -1 ENOENT (No such file or directory) [pid 13708] execve("/bin/diff", ["diff", "--horizon-lines=100", "--", "b", "c"], [/* 37 vars */]) = -1 ENOENT (No such file or directory) [pid 13708] execve("/usr/bin/diff", ["diff", "--horizon-lines=100", "--", "b", "c"], [/* 37 vars */]) = 0 ...
うぅむ。せめて絶対パスにしとけば変な diff コマンドが起動されてしまう可能性がなくていいと思うけどなぁ。 diff という名前の diff じゃないコマンドは作るんじゃない、ということか。
ところで、「diff を2回やって」という時点で、diff3 のアルゴリズムはなんか怪しいというか、洗練されてないというか、理論的な背景に乏しいというか、そういうことが疑われる。
diff は SES (Shortest Edit Script) や LCS (Longest Common Subsequence) と深い関わりがあるが、shortest とか longest とか、ともかく長さに関して限界な結果には複数のケースがありうるのである。
たとえば、記述を簡単にするために行単位じゃなくて文字単位で、aaabbb と bbbaaa の diff を考えてみよう。結果は「左の aaa を削除して右に aaa を追加する」というものか「右の bbb を削除して左に bbb を追加する」のどちらかになる。どちらにしても 3文字削除 3文字追加なので、変更量は変わらない。
でも、diff3 で使うふたつの diff の結果が異なる結果になったら何が起こるだろう? まぁ、決定的なアルゴリズムを使えば、入力が同じなら同じ結果になるからそこまで変なことは起こらない。でも、微妙にでも異なる入力であれば、結果がどうなるかは怪しいところである。
そういうことを防ぐには、おそらく diff3 の 3つの入力を一度に調べるようなアルゴリズムが必要となる。でも、そうなってないので、このアルゴリズムは怪しいわけである。
ただ、怪しくないアルゴリズムは困難すぎるという可能性はあって、あきらめて適当にやっているのがやっぱり正しいということかもしれない。
怪しくないアルゴリズムは、ナイーブには、おそらく O(n^3) ならできる。でも、O(n^3) は耐えられる限界を越えてる感じがする。
そういえば、たまに、許し難い diff に出会うことがある。
たとえば、こういうやつである。 (これは細工して作ったもので、この結果を diffutils の diff が生成するわけではない)
a.c 2006-04-07 11:53:23.000000000 +0900
+++ b.c 2006-04-07 11:53:21.000000000 +0900 @@ -1,6 +1,11 @@ int f(void) { fff; +} + +int g(void) +{ + ggg; } int h(void)
追加部分の範囲が関数の範囲とずれている。
たしかに、5行追加という意味では変更量は最小なのだが、この結果はわかりにくい。
そういえば、cvs の中には diff3 が入っている。では、そっちでも diff を exec するのだろうか?
と、思ってみてみると、exec してない。直接 diff_run という関数を呼んでいる。
cvs-1.12.13/diff/diff3.c:1321
1321: wstatus = diff_run (ap - argv, (char **) argv, diffout, my_callbacks_arg);
ただ、インターフェースがテンポラリファイルと argc, argv というのは exec してたときと変わらない。
cvs-1.12.13/diff/diff3.c:1290-1299
1290: ap = argv; 1291: *ap++ = "diff"; 1292: if (always_text) 1293: *ap++ = "-a"; 1294: sprintf (horizon_arg, "--horizon-lines=%d", horizon_lines); 1295: *ap++ = horizon_arg; 1296: *ap++ = "--"; 1297: *ap++ = filea; 1298: *ap++ = fileb; 1299: *ap = 0;
せっかく直接呼んでるんだからもっとましなインターフェースにしたほうが、と思わないでもないが、別に困るわけでもないというのも確かではある。というか、これでちゃんと動いているんであれば変える利点を主張するのは難しい。
そういえば、subversion の diff はなにを使っているのだろう。
調べてみると、Sun Wu の O(NP) のやつな模様。
subversion-1.3.1/subversion/libsvn_diff/lcs.c:28-33
28: * Calculate the Longest Common Subsequence between two datasources. 29: * This function is what makes the diff code tick. 30: * 31: * The LCS algorithm implemented here is described by Sun Wu, 32: * Udi Manber and Gene Meyers in "An O(NP) Sequence Comparison Algorithm" 33: *
read_diff であるが、pipe でつないで diff を起動した後は、pipe からメモリに読み込んでいる。
diffutils-2.8.1/src/diff3.c:1220-1239
1220: current_chunk_size = MAX (1, STAT_BLOCKSIZE (pipestat)); 1221: diff_result = xmalloc (current_chunk_size); 1222: total = 0; 1223: 1224: for (;;) 1225: { 1226: size_t bytes_to_read = current_chunk_size - total; 1227: size_t bytes = block_read (fd, diff_result + total, bytes_to_read); 1228: total += bytes; 1229: if (bytes != bytes_to_read) 1230: { 1231: if (bytes == SIZE_MAX) 1232: perror_with_exit (_("read failed")); 1233: break; 1234: } 1235: if (PTRDIFF_MAX / 2 <= current_chunk_size) 1236: xalloc_die (); 1237: current_chunk_size *= 2; 1238: diff_result = xrealloc (diff_result, current_chunk_size); 1239: }
まぁ、pipe の block size になんの意味があるのかという疑問はあるが、ファイルを全部読み込めるまでバッファを倍々に増やしていくありがちなコードである。
process_diff では read_diff の結果をなめている。
diffutils-2.8.1/src/diff3.c:957-958,962-963,1042
957: diff_limit = read_diff (filea, fileb, &diff_contents); 958: scan_diff = diff_contents;
...
962: while (scan_diff < diff_limit) 963: {
...
1042: }
process_diff には return がひとつしかない。
diffutils-2.8.1/src/diff3.c:952,959,1046
952: struct diff_block *block_list, **block_list_end, *bptr;
...
959: block_list_end = &block_list;
...
1046: return block_list;
その return は block_list を返しているが、block_list への言及は少ない。ってゆーか、一ヶ所で block_list へのポインタをとっているだけである。
block_list は struct diff_block へのポインタなので、block_list_end はポインタへのポインタということになる。
ポインタへのポインタで、list end という名前、とくれば、もう単方向リストを順に伸ばしていく感じが濃厚である。
struct diff_block を確認してみると、next フィールドが struct diff_block * なので、やはり単方向リストである。
diffutils-2.8.1/src/diff3.c:80-85
80: struct diff_block { 81: lin ranges[2][2]; /* Ranges are inclusive */ 82: char **lines[2]; /* The actual lines (may contain nulls) */ 83: size_t *lengths[2]; /* Line lengths (including newlines, if any) */ 84: struct diff_block *next; 85: };
ちなみに、lin というのは ptrdiff_t らしい。行番号に ptrdiff_t をつかうのはなんでだろう?
diffutils-2.8.1/src/system.h:327
327: typedef ptrdiff_t lin;
リストに追加しているところは次のようなところになる。
diffutils-2.8.1/src/diff3.c:962-964,1040-1042
962: while (scan_diff < diff_limit) 963: { 964: bptr = xmalloc (sizeof *bptr);
...
1040: *block_list_end = bptr; 1041: block_list_end = &bptr->next; 1042: }
ループがひとまわりするたびにひとつ要素を付け加えている。
あとはループの中身で bptr を埋めるのがわかれば、process_diff を理解した気になれる。
ただ、その前に、読み込む対象の例を見てみよう。
% cat f1 a b c d e f % cat f2 a d b f % diff --horizon-lines=100 -- f1 f2 2,3d1 < b < c 5c3 < e --- > b
この例からいくつかわかる。
・ 2,3d1 から □ f1 の 2-3行は f2 には無い、つまり削除 ([d]elete) □ f2 で対応する場所は 1行目の直後の行の間 ・ 5c3 から □ f1 の 5行目が f2 の 3行目に変わる ([c]hange) ・ 行番号は 1-origin
struct diff_block を見直すと、ranges はふたつのファイル内での開始位置と終了位置を inclusive で示している、とある。さて、inclusive とすると、行がない場合、例でいえば削除後の位置とか、をどう表現するかが気になる。
diffutils-2.8.1/src/diff3.c:965-970,978-991,995,999,1007,1015,1017,1024,1032
965: bptr->lines[0] = bptr->lines[1] = 0; 966: bptr->lengths[0] = bptr->lengths[1] = 0; 967: 968: dt = process_diff_control (&scan_diff, bptr); 969: if (dt == ERROR || *scan_diff != '\n') 970: {
...
978: } 979: scan_diff++; 980: 981: /* Force appropriate ranges to be null, if necessary */ 982: switch (dt) 983: { 984: case ADD: 985: bptr->ranges[0][0]++; 986: break; 987: case DELETE: 988: bptr->ranges[1][0]++; 989: break; 990: case CHANGE: 991: break;
...
995: }
...
999: if (dt != ADD)
...
1007: scan_diff = scan_diff_line (scan_diff,
...
1015: if (dt == CHANGE)
...
1017: if (strncmp (scan_diff, "---\n", 4))
...
1024: if (dt != DELETE)
...
1032: scan_diff = scan_diff_line (scan_diff,
process_diff_control で 2,3d1 みたいな行を解釈し、979行目でその行末の改行を読み飛ばし、scan_diff_line で < b とかの行を読んでいるのであろう。
process_diff_control ではまず、SKIPWHITE と READNUM マクロを定義している。
diffutils-2.8.1/src/diff3.c:1065-1083
1065: static enum diff_type 1066: process_diff_control (char **string, struct diff_block *db) 1067: { 1068: char *s = *string; 1069: lin holdnum; 1070: enum diff_type type; 1071: 1072: /* These macros are defined here because they can use variables 1073: defined in this function. Don't try this at home kids, we're 1074: trained professionals! 1075: 1076: Also note that SKIPWHITE only recognizes tabs and spaces, and 1077: that READNUM can only read positive, integral numbers */ 1078: 1079: #define SKIPWHITE(s) { while (*s == ' ' || *s == '\t') s++; } 1080: #define READNUM(s, num) \ 1081: { unsigned char c = *s; if (!ISDIGIT (c)) return ERROR; holdnum = 0; \ 1082: do { holdnum = (c - '0' + holdnum * 10); } \ 1083: while (ISDIGIT (c = *++s)); (num) = holdnum; }
うーむ。お子様お断り、ってコメントはどーなんですかねぇ。
まぁ、それはそれとして、SKIPWHITE は [ \t]* を読み飛ばし、 READNUM は [0-9]+ を読み飛ばすことがわかる。
diffutils-2.8.1/src/diff3.c:1085-1098
1085: /* Read first set of digits */ 1086: SKIPWHITE (s); 1087: READNUM (s, db->ranges[0][RANGE_START]); 1088: 1089: /* Was that the only digit? */ 1090: SKIPWHITE (s); 1091: if (*s == ',') 1092: { 1093: /* Get the next digit */ 1094: s++; 1095: READNUM (s, db->ranges[0][RANGE_END]); 1096: } 1097: else 1098: db->ranges[0][RANGE_END] = db->ranges[0][RANGE_START];
ふむ、ここまでで [ \t]*[0-9]+[ \t]*(,[0-9]+)? を読んでいる。数値は diff の結果の中の値がそのまま入り、数値がひとつしかない場合には、開始と終了両方がその値になる、というわけか。
diffutils-2.8.1/src/diff3.c:1100-1116
1100: /* Get the letter */ 1101: SKIPWHITE (s); 1102: switch (*s) 1103: { 1104: case 'a': 1105: type = ADD; 1106: break; 1107: case 'c': 1108: type = CHANGE; 1109: break; 1110: case 'd': 1111: type = DELETE; 1112: break; 1113: default: 1114: return ERROR; /* Bad format */ 1115: } 1116: s++; /* Past letter */
ここでは [ \t]*[acd] を読んでいる。
diffutils-2.8.1/src/diff3.c:1118-1132
1118: /* Read second set of digits */ 1119: SKIPWHITE (s); 1120: READNUM (s, db->ranges[1][RANGE_START]); 1121: 1122: /* Was that the only digit? */ 1123: SKIPWHITE (s); 1124: if (*s == ',') 1125: { 1126: /* Get the next digit */ 1127: s++; 1128: READNUM (s, db->ranges[1][RANGE_END]); 1129: SKIPWHITE (s); /* To move to end */ 1130: } 1131: else 1132: db->ranges[1][RANGE_END] = db->ranges[1][RANGE_START];
ここで読んでいるのは [ \t]*[0-9]+[ \t]*(,[0-9]+[ \t]*)? となる。
ぜんぶあわせると [ \t]*[0-9]+[ \t]*(,[0-9]+)?[ \t]*[acd][ \t]*[0-9]+[ \t]*(,[0-9]+[ \t]*)? となる。
diffutils-2.8.1/src/diff3.c:1134-1135
1134: *string = s; 1135: return type;
で、読み終わったらポインタを進める。進めた後は、まだ読んでいないところになるから、改行のところを指しているはずである。
process_diff_control の呼び出しをもう一度見てみる。
diffutils-2.8.1/src/diff3.c:968-970,977-991,995
968: dt = process_diff_control (&scan_diff, bptr); 969: if (dt == ERROR || *scan_diff != '\n') 970: {
...
977: exit (EXIT_TROUBLE); 978: } 979: scan_diff++; 980: 981: /* Force appropriate ranges to be null, if necessary */ 982: switch (dt) 983: { 984: case ADD: 985: bptr->ranges[0][0]++; 986: break; 987: case DELETE: 988: bptr->ranges[1][0]++; 989: break; 990: case CHANGE: 991: break;
...
995: }
やはり、まず読み飛ばしている改行は 2,3d1 とかの行の改行だった。
で、ADD や DELETE の場合は行番号をインクリメントしている。
ADD の場合は ranges[0][0]++ であるが、これは最初のファイルの開始行のインクリメントである。 ADD は最初のファイルになかったものが後のファイルに表れるというものだから、最初のファイルには対象となる行はない。そういうときに diff は (例には出さなかったが、DELETE のほうから類推できるように) 対象となる場所の直前の行番号が出てくる。なので、ranges[0][0] == range[0][1] で、そこにその行番号が入っている。で、インクリメントした結果、ranges[0][0] > ranges[0][1] で、開始行が終了行よりも大きくなる。
開始が終了よりも (ひとつ) 大きいのが空を意味するというのは、領域を inclusive で表現したときのやりかたである。まぁ、領域が空でないところから外挿するとそういう表現になるので、自然といえば自然なのではあるが、開始より終了が小さいというのは人間にとっては不自然気味である。個人的には対象とする状況においてよほどはっきりした利点がないかぎりは inclusive にはせず、終了をひとつ増やしておくことにしている。
inclusive に領域を表現する利点は、考えられなくはないのだが、diff3 で役に立つのはあるだろうか。あるとしたら範囲の扱いが開始方向と終了方向で対称になる、というあたりかな。
範囲を補整した後は、< b とかの行を読む。
まずは、dt != ADD という条件で元のファイルの内容である。 ADD のときは、元のファイルの領域は空なので、行はないはずである。
diffutils-2.8.1/src/diff3.c:997-1012
997: /* Allocate space for the pointers for the lines from filea, and 998: parcel them out among these pointers */ 999: if (dt != ADD)
1000: { 1001: lin numlines = D_NUMLINES (bptr, 0); 1002: if (too_many_lines <= numlines) 1003: xalloc_die (); 1004: bptr->lines[0] = xmalloc (numlines * sizeof *bptr->lines[0]); 1005: bptr->lengths[0] = xmalloc (numlines * sizeof *bptr->lengths[0]); 1006: for (i = 0; i < numlines; i++) 1007: scan_diff = scan_diff_line (scan_diff, 1008: &(bptr->lines[0][i]), 1009: &(bptr->lengths[0][i]), 1010: diff_limit, 1011: '<'); 1012: }
D_NUMLINES というのは以下のように定義されるマクロである。範囲を inclusive に定義したときの欠点のひとつが範囲の長さを求めるときに +1 しないといけないというところだが、ここではマクロに閉じ込めることによってその欠点に対処しているようだ。
diffutils-2.8.1/src/diff3.c:100-105
100: #define D_LOWLINE(diff, filenum) \ 101: ((diff)->ranges[filenum][RANGE_START]) 102: #define D_HIGHLINE(diff, filenum) \ 103: ((diff)->ranges[filenum][RANGE_END]) 104: #define D_NUMLINES(diff, filenum) \ 105: (D_HIGHLINE (diff, filenum) - D_LOWLINE (diff, filenum) + 1)
too_many_lines というのはエラーチェックぽいのでとりあえず気にしないことにして、その次で行数だけの領域を確保している。
で、その次で行数だけ繰り返して scan_diff_line で行を読んでいる。
scan_diff_line では、まず、leadingchar とスペースで行が始まっているかどうか検査している。さっきの呼出元では '>' を leadingchar として渡していたので、 "> " で始まっていることを検査していることになる。
diffutils-2.8.1/src/diff3.c:1286-1294
1286: static char * 1287: scan_diff_line (char *scan_ptr, char **set_start, size_t *set_length, 1288: char *limit, char leadingchar) 1289: { 1290: char *line_ptr; 1291: 1292: if (!(scan_ptr[0] == leadingchar 1293: && scan_ptr[1] == ' ')) 1294: fatal ("invalid diff format; incorrect leading line chars");
次に、その "> " の次のところを *set_start に書き込んで呼出元に戻している。呼出元では &(bptr->lines[0][i]) だったから、最初のファイルの i番目の行の先頭である。
diffutils-2.8.1/src/diff3.c:1296
1296: *set_start = line_ptr = scan_ptr + 2;
ふむ。そうすると、lines[0][i] は diff の結果全体を読み込んだ領域の途中を指すことになる。ということは、NUL terminate されてないよな、と思うと、そういえば引数の set_length や struct diff_block の lengths があって、ここではポインタと長さで文字列を扱っているわけだと考えられる。
その長さを求めるには行末を見つけないといけなくて、そのコードが次である。diffutils-2.8.1/src/diff3.c:1297-1298
1297: while (*line_ptr++ != '\n') 1298: continue;
これで行末が見つかった、と思うと、さにあらず。その次になんかコメントつきでいろいろ書いてある。
diffutils-2.8.1/src/diff3.c:1300-1321
1300: /* Include newline if the original line ended in a newline, 1301: or if an edit script is being generated. 1302: Copy any missing newline message to stderr if an edit script is being 1303: generated, because edit scripts cannot handle missing newlines. 1304: Return the beginning of the next line. */ 1305: *set_length = line_ptr - *set_start; 1306: if (line_ptr < limit && *line_ptr == '\\') 1307: { 1308: if (edscript) 1309: fprintf (stderr, "%s:", program_name); 1310: else 1311: --*set_length; 1312: line_ptr++; 1313: do 1314: { 1315: if (edscript) 1316: putc (*line_ptr, stderr); 1317: } 1318: while (*line_ptr++ != '\n'); 1319: } 1320: 1321: return line_ptr;
ふむ。最初に *set_length に書き込むのは想定の範囲内であるが、その次の if は何をしている? 行頭が \ だったとき?
あぁ、これはファイル終端が改行で終わってなかったときの話か。 diff の出力は行指向であるから、改行で終わっていない入力を与えられるとなかなか困る。行が改行で終わっているつもりで単に出力してしまうと、場合によっては出力が機械的に解釈できなくなってしまうことがあるからである。そういうとき、(GNU?) diff は改行を付け加えた後で、その改行は入力にはなかったということを示す印を次の行に出力する。その行が \ で始まるのである。たとえば、次のようなことになる。
% echo -n a > a % echo -n b > b % LANG=C diff a b 1c1 < a \ No newline at end of file --- > b \ No newline at end of file
この \ があったら、というのが件の if であるとすると、 --*set_length で長さをひとつ短くして改行を抜き、 \ で始まった行を読み飛ばすというコードになっている。 edscript というのが気にならないでもないが、きっとこれは出力形式の都合で改行がない場合を扱えないのであろう。
あと、\ の 1文字だけじゃなくて No newline at end of file というメッセージまで含めて比較するほうがいいのではないかという気もするが、このメッセージは実は locale 依存なのでうまくないのであった。
% LANG=ja_JP.EUC-JP diff a b 1c1 < a \ ファイル末尾に改行がありません --- > b \ ファイル末尾に改行がありません
そういえば、行末まで読む次のコードがあった。
diffutils-2.8.1/src/diff3.c:1297-1298
1297: while (*line_ptr++ != '\n') 1298: continue;
ここで、ファイル末尾のチェックをしていないのはいいのだろうか?
と、思い出してみると、read_diff に、ファイル末尾が \n でなかったらエラーというコードが入っていたのであった。
diffutils-2.8.1/src/diff3.c:1241-1242
1241: if (total != 0 && diff_result[total-1] != '\n') 1242: fatal ("invalid diff format; incomplete last line");
個々の行を読んだ後、次は dt == CHANGE という条件で、"---\n" という 4文字を読み飛ばす。--- はセパレータなので、削除した行と追加した行の両方があるとき、つまり CHANGE のときしか存在しないというのは理にかなっている。
diffutils-2.8.1/src/diff3.c:1014-1020
1014: /* Get past the separator for changes */ 1015: if (dt == CHANGE) 1016: { 1017: if (strncmp (scan_diff, "---\n", 4)) 1018: fatal ("invalid diff format; invalid change separator"); 1019: scan_diff += 4; 1020: }
そのあとは、DELETE でないとき、という条件で追加した行の読み込みである。
diffutils-2.8.1/src/diff3.c:1022-1037
1022: /* Allocate space for the pointers for the lines from fileb, and 1023: parcel them out among these pointers */ 1024: if (dt != DELETE) 1025: { 1026: lin numlines = D_NUMLINES (bptr, 1); 1027: if (too_many_lines <= numlines) 1028: xalloc_die (); 1029: bptr->lines[1] = xmalloc (numlines * sizeof *bptr->lines[1]); 1030: bptr->lengths[1] = xmalloc (numlines * sizeof *bptr->lengths[1]); 1031: for (i = 0; i < numlines; i++) 1032: scan_diff = scan_diff_line (scan_diff, 1033: &(bptr->lines[1][i]), 1034: &(bptr->lengths[1][i]), 1035: diff_limit, 1036: '>'); 1037: }
これは削除した行の読み込みとほぼ同じである。ただ、scan_diff_line にわたす leadingchar が < でなく > になっている。
これで struct diff_block を埋めたので、これをファイルの最後まで繰り返しやって、リストにして返す、というわけである。
make_3way_diff の signature は以下の通りである。
diffutils-2.8.1/src/diff3.c:535-537
535: static struct diff3_block * 536: make_3way_diff (struct diff_block *thread0, struct diff_block *thread1) 537: {
struct diff_block へのポインタをふたつ受け取って、 struct diff3_block へのポインタを返す。
struct diff3_block の定義は以下の通り。
diffutils-2.8.1/src/diff3.c:89-95
89: struct diff3_block { 90: enum diff_type correspond; /* Type of diff */ 91: lin ranges[3][2]; /* Ranges are inclusive */ 92: char **lines[3]; /* The actual lines (may contain nulls) */ 93: size_t *lengths[3]; /* Line lengths (including newlines, if any) */ 94: struct diff3_block *next; 95: };
struct diff_block と比べると、3つのファイルが関係しているために配列の長さが 3になっているという違いはいいとして、correspond というフィールドが追加されているのが気になる。
correspond は enum diff_type で、定義は以下の通り。
diffutils-2.8.1/src/diff3.c:68-77
68: enum diff_type { 69: ERROR, /* Should not be used */ 70: ADD, /* Two way diff add */ 71: CHANGE, /* Two way diff change */ 72: DELETE, /* Two way diff delete */ 73: DIFF_ALL, /* All three are different */ 74: DIFF_1ST, /* Only the first is different */ 75: DIFF_2ND, /* Only the second */ 76: DIFF_3RD /* Only the third */ 77: };
ADD, CHANGE, DELETE とか、2つのファイルでも出てくるものが入っている。それなのに diff_block にはこのフィールドがなかったのはなぜだろう?
考えてみると、ADD, CHANGE, DELETE のどれであったかは、行の範囲から求められる。最初のファイルでの範囲が空なら ADD だし、どちらも空じゃなかったら CHANGE で、後のが空なら DELETE である。
それに対し、diff3_block では範囲が空かどうかだけでは中身の種類がわからない。空でないのが 2つ (以上) あって、それらの内容が等しい、つまり CHANGE でないことが考えられるからである。場合分けして予想してみよう。
・ 3つ全てが空の場合はぜんぶ同じというか、まぁ、どうせ扱わないであろう。ともかく興味はない ・ 2つが空だとすると、残りのひとつが他のに対して追加されたことになる。そのひとつが他の 2つと等しくなることはありえない (空と空じゃないのが等しくなることはありえない) ので、きっと DIFF_1ST, DIFF_2ND, DIFF_3RD のどれかであろう。 ・ 1つが空だとすると、残りの 2つは等しいかもしれないし、等しくないかもしれない。等しくない場合は、3つ全部が違うのできっと DIFF_ALL な気がする。等しい場合は、空の奴だけが異なるわけだから、DIFF_1ST, DIFF_2ND, DIFF_3RD のどれかであろう。おそらく ・ どれも空でないとすると、全部が違うかもしれないし、どれか 2つが等しいかもしれないし、3つが等しいかもしれない。全部違うんなら DIFF_ALL で、2つが等しけりゃ DIFF_[123]* であろう。
3つが全部等しいときに割り当てるのがないのは、そういう部分はこのリストには含めないのであろう。diff_block のときもそうだったし。
最近、Google Reader を試しているのだが、とりあえず、tDiary のコメントが読みにくいことがわかった。
文脈無しにコメントだけいきなり出てきても理解できない。
HTML で読んでいる限りは、問題ないのだが。
コードを読み進める前に、ちょっと論文を探してみる。
とりあえず、複数の文字列に対する LCS は (文字列の数に対して) NP-hard らしい。
On the Approximation of Shortest Common Supersequences and Longest Common Subsequences, Tao Jiang, Ming Li, SICOMP, Volume 24 Issue 5, http://locus.siam.org/SICOMP/volume-24/art_0224067.html
あと、近似アルゴリズムもいくつか提案されているようだ。
まじめに diff3 の目指すところをやると O(N**3) で、 diff だと O(N**2) になる。最悪は。
いろいろと考えると最悪でももっとましなアルゴリズムのほうが現実的な気がする。
そういう意味では GNU diff のアルゴリズムに Paul Eggert が手を入れたのは正しかったように思う。
diffutils-2.8.1/src/analyze.c:23-34
23: /* The basic algorithm is described in: 24: "An O(ND) Difference Algorithm and its Variations", Eugene Myers, 25: Algorithmica Vol. 1 No. 2, 1986, pp. 251-266; 26: see especially section 4.2, which describes the variation used below. 27: Unless the --minimal option is specified, this code uses the TOO_EXPENSIVE 28: heuristic, by Paul Eggert, to limit the cost to O(N**1.5 log N) 29: at the price of producing suboptimal output for large inputs with 30: many differences. 31: 32: The basic algorithm was independently discovered as described in: 33: "Algorithms for Approximate String Matching", E. Ukkonen, 34: Information and Control Vol. 64, 1985, pp. 100-118. */
つまり、GNU diff の基本は Myers の O(ND) なアルゴリズムであるが、 Paul Eggert が細工して最悪を O(N**1.5 log N) に下げてあるのである。
ここで、D=N-L で、N は行数、L は LCS の長さである。つまり、L が小さければ O(ND) は O(N**2) になる。それが、O(N**1.5 log N) まで落ちるというわけである。
もちろん、これは結果が LCS にならない可能性があることを意味している。 --minimal を指定すれば細工が disable されて LCS になるが。
でも、--minimal が必要になった覚えはないし、現実的には完全な LCS が必要になることは多くないように思う。
また、diff の O(N**2) はなんとかなる場合もあるが、まじめな diff3 の O(N**3) はちょっと耐えられそうにない。
というようなことを考えると、はじめっから近似アルゴリズムを用意したほうが用途が広くて便利なのではなかろうか。
スライド 20〜30枚で 90分話すスキルはどうやったら身につくか?
ふつうのばんそうこうにはキャラクターグッズなどがあるが、キズパワーパッドみたいなやつにはあるだろうか?
と、キズパワーパッドをつかいながら思った。
世の中にはどんなばんそうこうがあるのだろうかと思って「ばんそうこう」で google image 検索してみると...
openclipart には地球がいくつかあるが、アメリカが中心になっているのが多くて、普段見慣れた日本中心のが見当たらない。
make_3way_diff の冒頭にはたくさんコメントがついている。
たくさんコメントがあるのは、コードがわかりにくいとか、あるいはわかりにくくならざるをえなかったとか、そういう書いた人のいいわけという可能性がある。ここはどうだろう?
diffutils-2.8.1/src/diff3.c:527-587
527: /* 528: * This routine makes a three way diff (chain of diff3_block's) from two 529: * two way diffs (chains of diff_block's). It is assumed that each of 530: * the two diffs passed are onto the same file (i.e. that each of the 531: * diffs were made "to" the same file). The three way diff pointer 532: * returned will have numbering FILE0--the other file in diff02, 533: * FILE1--the other file in diff12, and FILEC--the common file. 534: */ 535: static struct diff3_block * 536: make_3way_diff (struct diff_block *thread0, struct diff_block *thread1) 537: { 538: /* 539: * This routine works on the two diffs passed to it as threads. 540: * Thread number 0 is diff02, thread number 1 is diff12. The USING 541: * array is set to the base of the list of blocks to be used to 542: * construct each block of the three way diff; if no blocks from a 543: * particular thread are to be used, that element of the using array 544: * is set to 0. The elements LAST_USING array are set to the last 545: * elements on each of the using lists. 546: * 547: * The HIGH_WATER_MARK is set to the highest line number in the common file 548: * described in any of the diffs in either of the USING lists. The 549: * HIGH_WATER_THREAD names the thread. Similarly the BASE_WATER_MARK 550: * and BASE_WATER_THREAD describe the lowest line number in the common file 551: * described in any of the diffs in either of the USING lists. The 552: * HIGH_WATER_DIFF is the diff from which the HIGH_WATER_MARK was 553: * taken. 554: * 555: * The HIGH_WATER_DIFF should always be equal to LAST_USING 556: * [HIGH_WATER_THREAD]. The OTHER_DIFF is the next diff to check for 557: * higher water, and should always be equal to 558: * CURRENT[HIGH_WATER_THREAD ^ 0x1]. The OTHER_THREAD is the thread 559: * in which the OTHER_DIFF is, and hence should always be equal to 560: * HIGH_WATER_THREAD ^ 0x1. 561: * 562: * The variable LAST_DIFF is kept set to the last diff block produced 563: * by this routine, for line correspondence purposes between that diff 564: * and the one currently being worked on. It is initialized to 565: * ZERO_DIFF before any blocks have been created. 566: */ 567: 568: struct diff_block *using[2]; 569: struct diff_block *last_using[2]; 570: struct diff_block *current[2]; 571: 572: lin high_water_mark; 573: 574: int high_water_thread; 575: int base_water_thread; 576: int other_thread; 577: 578: struct diff_block *high_water_diff; 579: struct diff_block *other_diff; 580: 581: struct diff3_block *result; 582: struct diff3_block *tmpblock; 583: struct diff3_block **result_end; 584: 585: struct diff3_block const *last_diff3; 586: 587: static struct diff3_block const zero_diff3;
宣言の前に機能が書いてあり、宣言の後に、使用している変数の意味がひとつひとつ書いてある。
が、base_water_mark というのは説明されているけれど変数自体がない。昔はあったとかいう話だろうか。
こういう食い違いを (完全とはいわずとも) 防ぐためには、変数の説明は個々の変数のところにつけるのが良い。
スキルが身についてなくても一週間たてばまた必要になるのがナニ
% google-count --quote {土,水,火,風}のUSBメモリ 0 土のUSBメモリ 0 水のUSBメモリ 0 火のUSBメモリ 48 風のUSBメモリ
やはり30枚で90分話すスキルはついていなかった。
しかし、80分話せたので、先週の50分に比べればずいぶんましである。この調子で伸ばしていけば来週は 110分... 話すとオーバーしてしまうので、適当に手加減しつつ伸ばせば 90分になるのではないだろうか。たぶん。
さて、まず make_3way_diff の返り値をたどってみよう。
diffutils-2.8.1/src/diff3.c:581-583,590-591,597-598,676-682
581: struct diff3_block *result; 582: struct diff3_block *tmpblock; 583: struct diff3_block **result_end;
...
590: result = 0; 591: result_end = &result;
...
597: while (current[0] || current[1]) 598: {
...
676: *result_end = tmpblock; 677: result_end = &tmpblock->next; 678: 679: /* Set up corresponding lines correctly. */ 680: last_diff3 = tmpblock; 681: } 682: return result;
ふむ。例によってリストを伸ばしていくようだ。 while ループがひとまわりするたびに result から始まるリストがひとつ伸びるように、result_end がリストの終端のポインタへのポインタになっている。
伸びた要素は tmpblock なので、これがどう構成されるのかを調べることが何をやっているか調べることになる。
ただ、最後で tmpblock を last_diff3 に保存しているのは気になる。何につかっているのであろうか。コメントからすると、行番号だそうだが、どうだろうか。考えてみると、ふたつの diff の構造は一致している保証はなく、一方の diff のブロックの途中でもう一方の diff のブロックが始まったり終わったりすることがありうる。とすると、ちゃんとどこまで進んだか明示的に管理しないといけなくて、それに使っているというところだろうか。
last_diff3 の初期化をみてみる。
diffutils-2.8.1/src/diff3.c:587,593
587: static struct diff3_block const zero_diff3;
...
593: last_diff3 = &zero_diff3;
えーと、最初に zero_diff3 へのポインタで初期化してるが、 zero_diff3 に関する言及はここしかない。関数内 static 変数だから、他から言及されることもないので、 static 変数が 0 に初期化されるというのにまかせてやっているようだ。
diff3_block を再度みてみる。
diffutils-2.8.1/src/diff3.c:89-95
89: struct diff3_block { 90: enum diff_type correspond; /* Type of diff */ 91: lin ranges[3][2]; /* Ranges are inclusive */ 92: char **lines[3]; /* The actual lines (may contain nulls) */ 93: size_t *lengths[3]; /* Line lengths (including newlines, if any) */ 94: struct diff3_block *next; 95: };
ranges は構造体に埋め込まれている配列だから、ここだけを使っている限りは問題なさそうである。 lines, lengths, next のポインタは使えないから last_diff3 経由では使わないことが予想される。残りは enum diff_type な correspond であるが、diff_type において 0 は ERROR である。これは使われているかどうか予想できない。まぁ、もし使われているならドキュメントの意味で初期化しといたほうがいいだろうとは思う。
毎週火曜の非常勤講師の職場だが、さぬきうどんの綾から遠くないことに気がつく。それならいってみようかと思ったが、調べてみると火曜定休なのであった。
Guy Steele 氏の話を聞いてきた。
ループを読む。
diffutils-2.8.1/src/diff3.c:592,597-598,681
592: current[0] = thread0; current[1] = thread1;
...
597: while (current[0] || current[1]) 598: {
...
681: }
このループの条件節は current[0] || current[1] なので、ふたつの diff が両方終わるまでまわることがわかる。
さてループの中身である。
最初の行は何かの初期化である。
diffutils-2.8.1/src/diff3.c:599
599: using[0] = using[1] = last_using[0] = last_using[1] = 0;
さて、初期化するのはいいが、これは何を意味するのだろう?読んでいるときに、かなり読み進めるまでわからなかった。
わかった後にコメントを思い出して読み直すと、じつは書いてあったりしたのだが、これはコメントをちゃんと読めということか、わかっている人しか理解できないコメントであったということなのか微妙である。
まぁ、それはそれとしてどういうことかというと、 diff3 ではふたつの diff を混ぜるのだが、混ぜるときのブロックを表現しているのである。
ふたつの diff は共通の file を持っており、各 diff はそのファイルの各行について違うか同じかという情報を持っている。ここで、違うとされた行を * で示すと、たとえば次のようになったとしよう。
1 * 2 * 3 * * 4 * * 5 * 6 * 7 * * 8 * 9
10 11 * 12 * * 13 * * 14 * 15 16 * 17 *
このときに、違うとされた部分が重なっているところがある。そういう重なったところをたどって、たどり着ける限りのところをまとめたのが diff3 でのブロックである。この例の場合、1-8行目、11-14行目、16-17行目の 3つのブロックがある。
using がブロックの先頭、last_using がブロックの終端を表す。ただし、これらは行番号ではなく、struct diff_block へのポインタで範囲を表す。 diff_block は共通ファイルに対し複数行を占めることがあるが、そのときはそれらの行は連続しているので、diff3 のひとつのブロックの中に入る。なので、行単位で表現する必要はないのである。
JavaScript Security をざっと読む。
ちかちかプラネッツを読む。
4コマなのだが、おもしろいことに気がついた。
p.102 まではコマの横方向の間隔と縦方向の間隔がほぼ等しいのだが、p.108 から (と冒頭のカラー) は横方向の間隔のほうが広くなっているのである。
つまり、p.102 までは 1ページのコマの配置が次のような感じになっている。(番号は読む順番である)
+--------+ +--------+ | 5 | | 1 | | | | | +--------+ +--------+ +--------+ +--------+ | 6 | | 2 | | | | | +--------+ +--------+ +--------+ +--------+ | 7 | | 3 | | | | | +--------+ +--------+ +--------+ +--------+ | 8 | | 4 | | | | | +--------+ +--------+
それが、p.108 からは、次のようになっているのである。
+-------+ +-------+ | 5 | | 1 | | | | | +-------+ +-------+ +-------+ +-------+ | 6 | | 2 | | | | | +-------+ +-------+ +-------+ +-------+ | 7 | | 3 | | | | | +-------+ +-------+ +-------+ +-------+ | 8 | | 4 | | | | | +-------+ +-------+
そして、p.103 と p.107 も興味深い。この 2ページはタイトルページで、ページ上半分にイラストがあり、下半分に 4コマが入っている。
それ以前のタイトルページは例えば p.97 であるが、それは次のような構成である。
+--------------------+ | | | | | | | | | | | | | | +--------------------+ +--------+ +--------+ | 3 | | 1 | | | | | +--------+ +--------+ +--------+ +--------+ | 4 | | 2 | | | | | +--------+ +--------+
これが、p.103 は次のように 4コマの中心に N 字型の矢印がおかれ、読む順番を示している。
+--------------------+ | | | | | | | | | | | | | | +--------------------+ +--------+ +--------+ | 3 | | 1 | | | | | +------||\\ +||------+ || \\ || +------\/+ \\||------+ | 4 | | 2 | | | | | +--------+ +--------+
それが、p.107 では、矢印は消え、コマの横方向の間隔がひろがっている。
+--------------------+ | | | | | | | | | | | | | | +--------------------+ +-------+ +-------+ | 3 | | 1 | | | | | +-------+ +-------+ +-------+ +-------+ | 4 | | 2 | | | | | +-------+ +-------+
これらから読み取れることは、読者の読む順番を制御するために、まず矢印を用い、それから、思い直して間隔を調整したのではないかということである。
間隔を調整するのはべつに新しいアイデアではない。組み版の基本的な原則として、意味的に近いものを空間的に近く配置するというものがある。縦書だったら文字間より行間のほうがずっとひろいし、横書だったら逆である。それをコマに適用すればこの話になる。
そういう基本的な話を利用して読みやすくしているのは (そうなっていない作品も珍しくはないだけに) 努力が感じられる。
というようなことを考えながら最後にカバーを外すと、表紙と裏表紙に 4コマがひとつづつ載っていたのだが、この原則に反して次のような感じの構成でちょっとゲンナリ。
+--------+ +--------+ | 3 | | 1 | | | | | +--------+ +--------+ +--------+ +--------+ | 4 | | 2 | | | | | +--------+ +--------+
ところで、この原則は当然プログラムにも適用でき、空白、空行をどう使うかに関係する。
[latest]