プログラミング

2017/10/12- 片山泰男(Yasuo Katayama)
戻る∧ 開始=≫

目次

1. 光線追跡法
2. 知識と価値
3. プログラミング
4. ステレオ分析
5. SNRについて


≪=BACK TOP∧ NEXT=≫

1. 光線追跡法

生き方ということ、

光線追跡法というCGの手法が世に出たとき、私はAPPLE-II のBASICで画像を作り、4枚の画像を繰り返すだけで立体の動画像を作った。 その画像は、今ならリアルタイムで動画像も作成できる程度の画像だが、当時は、1枚の画像を作るのに数分か数10分もかかった。

簡単にいえば、球体が光の反射体で、ある高さの空と床が市松模様だった。それをある角度で眺めるような視点から見るのである。 画像は、視点から画素位置までの視線が仮想の空間のなかの反射物体でどのような反射をするかだけを模擬するものだった。 光線がある点からどの距離をもつかで球体に接触しないか、反射するかが決まる。反射すれば、床か空の光を写す。 計算は、ほとんどがその計算だけに費やされる。ある点という球体の中心座標は床との間を弾みながら進む。市松模様の1周期を 数枚の画像を繰り返すだけで画像は球体の緩やかな進行を表すようになる。APPLE-IIの高精細画像は、各点に4色表示できた。 ブルーとオレンジ、白と黒である。 3.579545 MHzの位相上に01,10,11,00がそれを表し、ビデオ信号を作った。

ASCIIの会社に入る前、1983年頃、それを書き、これが月刊LOGIN(1983.12?)の記事にもなった。社内にこれのソースを欲しいという同僚がいた、 優秀な人だったが断った。なぜか。手元にすぐにそのソースが出せなかっただけでなく、私の趣旨を理解しなかったと思ったからである。 画像は計算法を示していた。それを見れば計算法は自ずと考えれば到達できる。そして、ソースをもらうことのできる人を前にして、 その人は礼儀を失っていると判断したのである。

その方の生き方の問題であったから。人が知識を得る時、礼儀など必要ではない。強奪することでもよい。正しいのは知識であって その所有ではない。しかし、知識は正当な手順を経なければ決して得ることはできない。自ら考え、到達するということである。

いま、これをいうのは、【衝撃】貧乏になる人の8つの共通点… 嘘のようで本当の話 をみて、私は自省するからである。世界は、与えないと得ることはできない。


光線追跡法は光の逆辿りで、全て高校の数学だが、視点 p0=(x0,y0,z0) と画素p1=(x1,y1,z1) の2点を通る直線、(x-x0)/l = (y-y0)/m = (z-z0)/n の方向 (l,m,n) を求める。v= p1-p0= (l,m,n) = (x1-x0, y1-y0, z1-z0) である。球の中心a=(a,b,c)点と直線の距離d。 ベクトルdは、視点から球中心までのベクトルa-p0と正規化vとの外積、
d^2= |(a-p0v|^2/|v|^2 =[{(b-y0)n-(c-z0)m}^2 + {(c-z0)l-(a-x0)n}^2 + {(a-x0)m-(b-y0)l}^2]/(l^2+m^2+n^2)。
もし、d<球の半径(r0)なら、光は球面上の点で反射し、

b= a-p0
c1= |b|^2 - r0^2
b1= bv
p1= (b1-√(b1^2-c1))v+p0
k= -2v・(p1-a)/r0^2
v+= k(p1-a)
v/= |v|
p0= p1

mで床(y= ...)か天井(y=...)の高さの(x,z)が市松模様の2色を決める。床と天井の市松模様が湾曲して現れ、球の赤道部分に遠方が微細な模様になる。 後に、ワークステーションで動画用のYUVファイル(720x486)に書き出すように直し、リアルタイム以上に高速動作したが、その画像サイズのCGの質としては、 天井の市松模様の一方は厚みのある立体板で他方は穴を抜けた空にしたかったが、2重天井と2重床にしていた。 ray.c(1994 5/21)
$cc -o ray -O ray.c -lm
$./ray file 0 149 でfiler000.yuv〜filer0149.yuvが作られる。

ファイルを大量に(5秒で100MB)生成し、yuvファイル表示プログラムも要るので、表示プログラムと合体:ray1.tgz(2017/11/18), 画像サイズを16x16の倍数で自由にした: ray2.tgz(2017/11/19), 色彩が正確に出るよう rgb の色テーブルを作り調整し、yuv に変換 し表示: ray3.tgz (2017/11/19夜), ray4.tgz (2017/11/21), ここまでは yuv を経由したが、rgbを直接 XImage に書く: ray5.tgz(2017/11/21), 水平に1画素あけて光線追跡を計算、前と同じ画素値なら、間を同じ画素値で埋め、違うと き間を計算。平坦なとき処理が半分になる(#define half)。同じ事を2次元で行い、垂直にも2つ上と同じなら1つ上を埋め、違う場合に計算、左上画素にも行う。 平坦なとき処理が1/4になる(#define quarter)。main.cの先頭でdefineする。遠方の細かい部分が少し汚くなるが速度向上する。halfで2/3の時間、quarterで 半分以下。720x480で1000枚21秒〜50Hz、1280x800で1000枚53秒〜20Hz(Core2duo): ray6.tgz(2017/11/23), 720x480で1000枚17秒〜60Hz、 1280x800で1000枚41秒〜25Hz:ray7.tgz(2017/11/25), ray8.tgz(2017/11/27), ray9.tgz (2017/11/29), まとめ ray10.tgz(2017/12/3)
$tar xvfz ray10.tgz
$cd ray
$make

その彼は、私がASCIIに入ってすぐにやったアセンブラでの仕事で、ニーモニックが大文字でないことにクレームして私に直させようとした。 「あなたは仕事を受けたのだから、要求に答える必要がある」と、私に必要のない修正を要求した。まるで社外の契約仕事のようだ。さらにずっと後だが、 動画の表示ソフトを私からもらって、それは当時、2種のディザの切り替えで色の再現性を上げようとした表示ソフトを2種のディザの切り替えでなく、 元のソフトにして欲しいといってきた。両方とも、私は断った。そうしたければ、自分で直して欲しい。私は、いまそれをする時間も無(ければ、 その義務もない。さらにそうしたほうが良いと決して思わな)いから。


≪=BACK TOP∧ NEXT=≫

2. 知識と価値

知識の価値、知識は誰にとってどのような価値となるか。について考えよう。私は何度もカンニングされた。本当にカンニングしているのをみた。 テスト中に隣の席の同級生の顔を見ると一点に視線が止まっている。その先は私の机の上である。私はやめろと声を出して、その声に周りの人が 驚いた。彼にとって目から入る知識は、テストにだけ関係したのだろう。私はその彼の顔をまだ思い出す。彼に対する落胆が強いからであろう。

大人になって、会社での仕事においても、辛辣な言い方であるが、仕事を理解しそれに十分に対処しない人、言語でプログラムを書けない人、 毎日長時間のプログラムとデバックができない人は、安易に結果を求め、完成品の技術を入手しようとする。 出向者の集まりの会社で出向元に出す特許を、私から無理やり聞きだした知識で特許申請した同僚、これは許しようがない。 その会社からはそのような人が来ていた。何もないところからプログラムを作成することを "from scratch" スクラッチ(引っ掻き)から プログラムを書くという。プログラムは「ひっかき」から始まるのである。ぼんやりした概念でスタートして、まず計算機言語で表してみて、 それによって、プログラムが備えるべき必要な機能を知り、望む結果でないときは、自分のバグか、自然のモデリングの誤りか、相手の使用法、 操作ミスかを判別しないといけない。仕事上のプログラムで自分のミスのときはバグフィックス(修正)しないといけない。

プログラムを作り、デバッグし、シミュレーションを繰り返す地道な技術的な作業(=エンジニアリング)が必要なところに、出向から社に戻る時期に 同じ社内だからMPEG-2提案に使ったCプログラムのソースをくれるのが当然と公然と主張した人たちの愚かな図々しさには閉口した。彼らは私と同じ 期間に同じ会社にいて何をしていたのか。彼らは技術者になれなかったのに、技術者としての成果を持ち帰りたいのである。それを与えたとしても、 彼らはそれを受け継ぐこともできないだろう。彼らの社内には本物の技術者がいて、それに威張って成果を渡せば社内を騙せるとでも思っているのか。

同じようなことが、MPEG-2提案に共同研究をしたW大学にあった。まずソースを欲しがり、(後の別のS大学との話だが、渡すと学生がソースの中の 名前をまず変更しているのを私は見た。それはすべきでない。そしてソースを購入した訳でもないのに、ソースの説明を求め、バグを発見して も修正をなぜか私に求める...ソースを読んで理解し、バグなら修正しようという努力をしない。)、私が符号化効率を追求、相手は階層符号化を 追求するので、その時点から別々の提案にすることにした。ソースは一部は利用できるだろう。協同作業は殆どなくなったが、会合しても、非イントラ は逆量子化に中間値を返すから量子化に丸めが要らないことを説明しても決して理解しようとせず、従わず、私がフィールド間予測に使った 画素中間位置の内挿フィルタに[-1,5,5,-1]がよいといったことを、階層画像の縮小フィルタに用い(それでは通過帯域が広すぎる)、最後にはその 協調を信用せず、私がボケボケの結果を仕組んだと疑ったという。なぜそういうことになるのか。プログラムを少し修正して確認すれば分かること であるのに、すぐに画像で確認ができなかったら、SNRの数値だけでも符号化の道具の良し悪しは判断できる。口達者なNHKもそうだったが、同じ レートでボケボケの画像は何か大きく間違っていることだけを示す。その状態ではどの道具(または道具の構成)も信用できない。2つ道具があるとき 1つの道具は最良に調整して初めて次の道具の調整に移ることができる。それでもまだ最良ではない。互いに影響するパラメータでありえるからである。 後に私が思うのは、単に我々両者の意思疎通に問題があっただけでなく、彼らの符号化の知識とプログラムの技術に対する欲求が低かったのである。 技術者は、本当のことが何かを求めなくてはいけない。そして、他人の行為の善意を信じられず、自分の努力に対する信頼がないのである。 計算機言語の道具を使って、正しく努力すれば、凡人もどこまで仕事上の山を登れるかを信じるべきである。


知識は、それ自身に意味がある。何かの世界の客観的な事項の認識方法を与える知識は、それを見出した人だけでなく、誰にとっても有益であり、 価値あるものになる。個人的な知識は、職業訓練の意味の場合もあるだろう。しかし、知識は直接的な価値ではない。それ自身に意味があるが、 価値ではない。価値は、関係性によって決まる。必要な人は代価を支払えばよいが、支払うだけで知識は手に入らない。納得できるように体系を 構築するにはその段階を支える前の段階がいる。知識が正しく納まるためには、個人は知識を自分なりに整頓する必要がある。 未整頓の知識は本来の働き(その次の技術の開発や法則の発見)を発揮しない。知識は再構築のあるべき場所に納まって初めて、その次が可能である。

それは社会に出るのに役に立つためでもない。勿論、その役割と意味もあるが、それが知識獲得の目的ではない。さらにいえば、知識はそれ自身のため でないとその意味がない。頭を整理するためにだけでもよい。行動を正しくするためでもよい。誤った行為を避けるためでもよい。自分のためであり、 ひいては共同社会のためでもある。そのようなものしか頭に叩きこむ意味はない。私のように中学高校のどこかで円周率を40桁覚えても、 最初の8桁以上が役に立つものではない。100桁覚えている友人がいたが、円周率の暗記は、それ自身に全く意味はないが、 知識を求める意欲は明確に表していた。その友人とは高校から最近まで親交し、永く議論してきた私の数人の半理解者のひとりである。 何かを行うには、それを支える無用な知識が沢山沢山必要である。その無駄な知識に円周率があっただけである。私は彼からマシンのコマンドの 意味や扱い方をどれだけ教えてもらったことだろう。そのエンジニアとしての無償の労力に私は感謝しなければならないのである。


≪=BACK TOP∧ NEXT=≫

3. プログラミング

現代の工学は、ほとんどプログラムによる制作である。それがソフトウエアであっても、ハードウエアであっても、LSIの設計もverilogなど の高級言語(これもC言語で処理される)による制作である。エンジニアは、朝から晩まで何らかのディスプレイの前で、キーボードを叩く作業 でできている。高級言語は、C言語ができてから、殆ど進化しない。Web上から、各PCで動作するものも、それに近い言語JAVAで書かれる。

インタプリタからコンパイラへの速度の向上は大きい。インタプリタは、実行時にソースの各行の解釈を伴う。昔のBASICインタプリタや、 FORTRANの言語は、行番号で整理し、行番号の昇順に実行される。ループはFOR文以外は条件付きGOTOである。WHILE文もあったかもしれないが。 行をのちに挿入するには行番号を開けていないといけないので、大規模なプログラムは扱いにくい(言語が中途半端な編集機能をもっていた)。 変数名はIJKLMNで始まるものは整数。それ以外は実数だった。改良されたFORTRAN(RATFORといった)は、変数名や配列名に制限がなくなった。 行番号にラベルがつき、サブルーチンを呼ぶのに行番号ではなくラベルで呼ぶことができるようになった。

C言語では、関数の名前 func1(arg1,arg2,..) がそのままサブルーチン処理を意味するようになった。その処理は関数と呼ばれ、その中身は、 (int func1(a) int a; { int b; b= a; return a*b; } ) のように(先に)定義される。関数は決まった数の引数をつけて呼び、関数は関数型に合わせた 戻り値を返す (int a; a= func1();) 。処理のステートメントは値を持ち代入できる(c= b+= a;)。処理の基本は逐次処理で、たまに並列処理記述用の 並列化C言語が使われる。

3.1 構造的プログラミング

構造的プログラミングという構造のない言語の記述は、同一処理を多様に書くことができ、その読みやすさのための構造ルールを(自分用に)作るものである。 書き方は読み易さのためだけのものであるため、その作法の違いを非難する瑣末なトラブルが起きる。書法が違うために読みにくいと思う拒否反応である。 プログラムが巨大になってくると、人は読まなくなる。そして、構造の分かりにくさを避けるため、変数名も関数名も単に区別さえできればよいのではなく、 処理の意味を正確に表す短い名前(単語)がよい。変数名、関数名、配列名は、プログラム中に何度も表れるものである。文章のような長い変数名は、再度 決して読まれないから避ける。特に関数内部のローカル変数は関数の外から参照されないから、重複を心配せず短くしてよい。他のグローバルな名と重複 しなければよい。10文字の変数名は、1文字の変数名の10倍ソースが大きくなるだけで、コンパイル結果は同じものを出すべきものである。

実際の処理は、できるだけ無駄を避け、簡潔にしないと間違いやすく仮に結果が正しくても、処理時間が掛かり非実用的になる。gotoはできるだけなくし、 ループはできるだけ分かりやすい for文、次にwhile文、最後にdo{}while()文を使う。for文は、for(i=0;i<10;i++){...} のように、for(ループの入口 処理;ループの先頭判断; 各ループの最後の処理){...}で、先頭判断で条件を満たす間ループを繰り返す、i=0; while(i<10) {... i++;} と同じ。 do{} while()文は後尾判断で処理は1度は実行される。i= 0; do{ ... i++;} while(i<11); が同じ。変数の後の++は、使用後に1増加。前の++は使用前に1増加。 --も同様に減少。

return; を使う複数出口を避ける。アセンブラでは普通だった複数入口は、言語がほぼ禁止しているが、出口は return; がどこでも可能なので、 出口をひとつにする努力をする。そのためにはgotoも使う。その目的は、プログラムの可読性を上げることである。 スパゲッティのようになって流れが見えないのを避ける。int を返す関数から出るには、return i; のように値を返す。

2次元配列は、char img[480][720]; と、関数の外にグローバル配列を置くと、img[j][i] は、右の添字iが0-719に先にならび、次の左の添字jが0-479にならぶ。 img[0][719]の次はimg[1][0]である。for(j=0;j<480;j++)for(i=0;i<720;i++)img[j][i]= 0; がメモリー上に順次である。

#define aaa bbb は、プリプロセッサ(cpp)が aaa を bbb に置き換える文字列処理をする。#define a(b,c) {b+= c;} は、関数をdefineで表すものでインライン 関数 inline a(b,c) {...} と同様にメイン側に関数がインライン展開されるが、関数の inline 展開は、makefile の中のコンパイルの最適化 -O3 で自動的に行われる。

C言語は、最低限 1つの xxx.c というソースを必要とし、その中に、1つだけ、main()という関数が必要である。一般的な形は、main(int argc, char **argv)である。 実行時にmain()は、argcは実行時のコマンドに付加するアーギュメント文字列の個数+1を与え、argv はその文字列配列のポインタを与える。argv[0] は実行 コマンド名を指し、argv[1] 以降がアーギュメント文字列を指す。引数なしの場合、main(){....} でもよい。

利用するライブラリ関数によって必要な、#include <stdio.h>などをソースの先頭に付ける。main() のなかで他の関数を呼ぶ。他の関数は先に「定義」しないと、 関数の型や、引数の数、型を与える関数の形式を先に示す、関数の「宣言」を必要とする。だから、main() はソースの最後に配置するのが単純な方法である。 そうでないと、関数の型と引数の型を示す中身なしの関数の宣言を、それ以前にヘッダファイル main.h などで付ける必要がある。

cppによって最初に除去されるコメントは、必要最小限に、誤りのないようにする。コメントは大抵信用されない。希望を書いているだけで未実現だったり不要 だったり、プログラムと矛盾する間違いだったりする。日本人技術者は英語が不得意で明確なコメントを書けないし、プログラムの動作は日本語でも正確に言え ない。c++の行末までのコメント //... と /* ... */ という2種のコメントがCで使える。c++ は使いたくないが c++ のコメント// は使いたいという人が多い。 プログラムのテストの間に書いた代替の行を残したコメントは最終的に綺麗に掃除すべきだが、途中段階では残るものである。

プログラムを綺麗にして、コンパクトにするのは、自らバグを生まないためである。常に、思ってもいないようなバグが起き、一度バグが起きれば、そのバグの フィックスにはプログラムを書く何倍もの時間と労力と気力が費やされる。一般にバグ取りは素直なプログラム以上に難しい。さらに、プログラムは、改良、 改変されるものである。改良する人の理解を促進するには分かりやすいプログラムにしないといけない。そのような作業を散々、経験したプログラマーは、 コンパクトな書法になる。但し、これは人に依る。これに困るという方も多い。ある1文字か2文字の変数名は、エディターで検索できないという。 vim では * で一発なのに。


3.2 プログラム作法

また、カギ括弧 { で始まり、} で終わる、複数ステートメントのまとめは、ループの記述に多く使われるが、閉じる } を書く位置には異論が多い。 私は内部ステートメントの始まり位置に並べて書く書き方だが、ループのステートメントforの始まり位置に書く人が多い。その同僚からの苦情には、 MPEGでは上の形式が一般的、といっても納得されず、ダイクストラの "プログラム作法"の書き方だと本を示して、彼は驚き、初めて納得された。 彼は書方の教科書を読まずに書法を対人批判して間違っていた。それでも習慣を変更するには抵抗がある。そして、内部ステートメントの終わりを 示すには上がよく、タブを1つ節約できる下もよい。単に習慣でしかない。私も人の習慣にあわせて下の書き方をすることが増えた。その同僚は、 私が書き方を変えたのをみて「その方がよい」というが、次のことを教えてくれたのには感謝する。cコンパイラ cc (gcc)自身が警告のレベルをもつから、 文法上の警告レベルを最大に -Wall としておき、警告は(できるだけ)全てなくなるように書き直す。さらに特別に警戒するには、曖昧な書法をチェックする lint (glint) をたまには使うこと。但し、lintはそのような見た目の違いに文句をいわない。もっと本質的な誤りの可能性を見出すものである。
for(i=0;i<N;i++){
	a[i]= b[i]= c;
	}
for(i=0;i<N;i++){
	a[i]= b[i]= c;
}
プログラムが思った通り動作してから行うのは、ソースを見やすく綺麗にするためよりも、速度向上のための書き直しが多い。無駄な動作をなくし、 少しでも速度向上に注力するのは、コンピュータに扱わせるデータ量が限界的なためである。毎日、夜中にプログラムを走らせ、 明日朝までに全データの結果を出したいとき、コンピュータの速度が要る。そして、ソースの書き方で速度は変わるものである。 プログラムの速度向上には cc(gcc) -g でコンパイルし、実行時に mon.out(gmon.out)を作成し、prof(grpof)の実行時の時間的ルーチン使用比率をみて、 比率の大きい主要なルーチンを知ることが重要である。主要ルーチンは改良し速度を上げると、全体の速度が向上する。小比率のルーチンは改良しても、 改良の効果は小さい。

3.3 リエントラント

呼ぶ側がアーギュメントやバッファを用意する作法は、リエントラント(再入可能)といい、マルチタスクの記述を可能にした。関数コールのとき、 どうレジスタが使われるかは言語実装によるが、外部要因で割り込みが起きるとき、割り込みを不許可にしない命令間では割り込みされる可能性があり、 割り込みプログラムは最初どこまでのレジスタを退避すべきかを決めておく。簡単な処理なら退避と回復処理もそれだけ少ない。 退避と回復のステップを含めた命令数が少ないほど高頻度の割り込みができる。100MHzのクロックのマシンは、601形式の1マクロブロック20μsec の間に2000ステップまで使える。関数は呼ぶ側にデータのバッファをもてば、呼ぶことの重複さえ可能になる程度に退避処理を小さくできる。 Cプログラは、さらに大規模なプログラムのために、クラス付きの C++に変化していく。


3.4 エディター

ハードやソフトの設計がプログラムで行われるということは、プログラムの記述には、エディタのプログラムが大きな働きをし、UNIXではvi(またはvim)が使われた。 その他も多く使われただろうが、日本語には昔から日本語コードの問題があって、コードによらないようになっているが、私はずっと、NEWS ではjvi、LINUXで jvim を永く使った。いま vi(vim) の上で記述している。エディタの変化に対応すると指の反射的な操作が違ってきて、意識するからである。

viで最低限覚えること、 vi file でfileを編集(なければ作成)する。カーソル位置は h,lで左右、j,k で上下に移動する。xはカーソル位置の1文字を消す。dd は 行を消す。shift-d はカーソル位置から行末まで消す。uは1つ変更前に戻る。control-r で先の変更を再度行う。vim ではそれが複数回可能である。i,a又はoで挿入 モードに入り、iはカーソルの前(aは後、oは次の新行)に文字がはいる挿入モード。挿入モードから出るのはescであり、hjklでカーソルの動く元の状態に戻る。 viの終了は、shift-zz (shift を押したままz 2回)保存終了。:で左下に:がでた編集モードで行うq,q!などである。:q はquit変更無し終了。:q! は変更無視終了。 元の状態から :ではじまる編集モードは、数字 return でカーソル行移動。:の次は範囲指定(なければ全域)。1,10 は1から10行まで1,. は1から.現在行($は最終行) まで。範囲指定後に先のq,q!以外にr,w,dがある。wは書き込み。rは読み出し。dは消去。:w!は読み出しモードviのviewの強制書き込み。:w file は全域(1,$)をfile (なければ最初のvi fileのfile)に書き込み。:1,10s/aa/bb/gは、1から10行までに、aaをbbに置き換え。元の状態から /で始まるのは検索で、/aaa は、aaaを検索し カーソル移動。 /又はnで次検索。*では区切り付き、カーソル位置の変数名などを検索。

大学の研究室にはミニコン PDP-12 があったが、スイッチで1命令ずつブートストラップをいれ、紙テープからプログラムやデータを読む。いかにも不便と騒音で 私は殆ど慣れなかった。家で紙テープリーダーを作った。高級言語は大学の大型が使えた。FORTRANを学び、カードをパンチし、処理依頼して、プリンタ出力を持 ち帰る。私がそれをしたのは、文字でプリンタ幅に減衰振動を表す指数型関数を打ち出した1回きりである。その後、初めて研究室にワープロが導入されたが、 私は手を付けなかった。8008でコンピュータを制作し、RCAのCOSMACを解説した。8080になりそれ用の高級言語4K BASICをビル・ゲーツが開発した。 APPLE-II の時代に、私は初めてキーボードを使うようになった。プログラムは保存できる。文章も保存できる。自作した素材は活用できる。他人のソフトも利用 できるかもしれない。PC9800を仕事で使い、MIFESエディタを日本語入力に使った。

会社ASCIIではDEC10だったか、冷房が20度設定の騒音のコンピュータ室の贅沢な扱い巨大な交換型ハードディスクとテープ記憶装置。Zエディターの機能。私はそれら に親しめなかった。GCTでワークステーション SUN3, NEWS, magnum を使用し、文書整形は、Latex が使えるようになって質的な不満はなくなった。1980年代の 後半、それで学会発表をして、他の日本語ワープロソフトを使っていた大学の中では目立って綺麗な文書が作れた。仕事の作業の大半がプログラミングである ことは、その頃から始まる。ベクトルマシン(Stella, Titan)や、その後のGCLではDEC Alphaを使用した。

3.5 ユーザーインタフェース

ユーザーインタフェースは、ビットマップディスプレイ、キーボードとマウスという組み合わせは1960年代にSUNのstarで開発されたものだが、それがいまだに全 世界で使われる。音声入力はまだ(少なくともプログラミングには)使われていない。人工知能はまだ表面だけである。ニューラルネットワークは、まだ使われてい ないといったほうが正しい。その束をどう結合すれば、どういう問題に役立つかという設計方法を概略しか私は知らないし、超並列処理を単純な浮動少数点演算の 積和で実現したシミュレーションは、速度の問題がある。本格的に実施するには、ハードウエアのニューロンでなくてはいけない。ニューロンのランダム結合が 何か生み出すという期待は、私も初心者のときコンピュータと乱数で与える命令で何か起きると期待しただけに終わったことに通じ、何も生まないだろう。


3.6 人工知能

量子コンピュータは、まだ詐欺状態である。人工知能も似た状態である。もし、あなたが反論するなら、全入出力を音声(又はテキスト)にして、内部は全てニュー ロン結合で、九官鳥を賢く(訓練)するような方法で学習させただろうか。内部に人為的な要素が多いのではないか、チューリングテストは衝立の前で人は知能を人工 と判別できる/ないことをいうが、知能の真似をするための設計は知能でない。ハードウエアは人為的に用意してもよいが、ソフトウエアは論理の実装、現実化であり、 記述に労力を要し、実行時の処理はもっと想像を超えた複雑さをもつものである。どれほど危うい(意図した目的と結果の間の違いの、解釈の難しい)バグだらけに なり得るか経験した人が知る、その"プログラミング"というものは、教師なし学習(=自己組織化)でないといけない。つまり、プログラミングそれ自体がなくなり、 学習は知識をデータとして示すだけ、東大生研の原島先生のニューラルネットに対して"設計の放棄"といわれた批判の通りでないといけない。我々は、全くの外部 論理だけにより、ヒューリスティック(人為)を排除し、内部設計を放棄しただろうか。(現在のは、単語単位の音声認識と、ネット検索による概念結合と、発話機能 の組み合わせだろうと推測する。そして、人為的なAIには進歩がない。60年代に言われた「音声タイプライター」がまだできていない。)

3.7 パターン認識

パターン認識用語としての学習は、パターンの例示によるパターンの判定であり、通常は問題パターンを提示し答えを教えるから、その結果がよいのは当然である。 単純パーセプトロンのような線形判定関数では多次元空間の超平面での分割でしかなく、それが常に学習(を完了)できることが証明されている。その次の段階の 多次元空間の曲面分割や複数平面による分割がパーセプトロンの多層化によるパターン認識であるが、その明確な学習方法がないと否定的に教えられた。 非線形素子の細胞の多層を学習させるバック・プロパゲーション(逆伝播学習)は、可能な方法であるが、勾配法であり、その局所解は一般にグローバルな最適解でないと 否定された。 単純パーセプトロンが確実に解に到達するという前例に比較しての意味であるが、そこには線形分離可能な場合、つまり解ける問題の場合にという、大きな制限があった ことを忘れてはいけない。最初から解が分かっているならそれで与えればよい。そうではなく学習で行うことは、例示による汎化である。人間が知識を持たないから 例示から解を汎化で得るのであるから、一般的な最適解への学習方法がないことは証明さえできるだろう。機械に与えた問題の提示パターンと答えのペアにどのような 正当性があるか問題にせず、人間がどのような無理な問題と答えのペアを与えても正しく答えるマシンは、答えを記憶したマシンだけであり、それを目標とするのは 意味がないのである。教師あり学習はその意味で正当な学習ではない。


3.8 教師なし学習

教師なし学習は、望む判定結果(人為的なカテゴライズ)を与えない学習である。それまでに示した例示パターンを記憶し、多次元空間で自動クラスタリングする、 例えばどのグループに属するか入力パターンとグループ代表との距離の最小でグループ分類し、そのグループに統合するには例えば代表点を入力点に一定比率で 近づけるような過程(k-means法という)がある。その例として、画像符号化でよく使われた、ベクトル量子化の代表ベクトルを学習で生成するLindo-Buso-Grey によるLBG法がある。初期ベクトルから、距離に依る垂直2等分面分割と、各グループの重心による代表点形成を繰り返し、ボロノイ分割に漸近させる。 ニューラル・ネットにおける重み学習と、ベクトル量子化の代表点移動は、前者は非線形写像の実現、後者はグループ分類であるが、関係がないとはいえない。

3.9 設計の放棄

ニューラルネットへの批判として彼は "設計の放棄" をいわれた。大抵の技術は設計が伴うもので、例えばプログラムは論理的に記述しないまたはできないとき、 バグを生む。しかし一方、そうでない知識のなかで我々は生きている。機械に明示的に記述できない知識が多くあり、設計者は、多くの状況を想定してプログラム するが、知られない例外的な状況はつねにある。例えば、高級言語によるLSIの記述はC言語によるC-モデルという手本との間に入出力が一致する限りにおいて 正しいのであり、LSI製品の検査は、例示入力のときの期待値(テストベクター)との一致をもって検証する。正しい検証手段として、例示しか認めないのである。

3.10 知識データベース

昔(1986年?)、ASCII社内に、かな漢変換の仕組みを使って英語への翻訳機能を付けようとしていた人がいた。そのとき、知識は個々にプログラムされることに近かった。 当時誰もが、かな漢変換の仕組みの完全な理解が必要で実現は複雑で難しいことだが、かな漢変換の候補に英単語を用意して選択させれば、日本人が英語を書くのに 随分と役立つよいアイデアと理解できた。しかし、知識がコンテンツでなくプログラム設計に表れるのは、まず何か違うと思い、それが人間によるプログラミングなら 知識の直接実装であり、これは明らかに膨大になって人工知能に向かないという批判もされていた。 知識を一つ一つ人間が設定するのは、余りにも多量のプログラミング作業がいる。 そこで、知識をコンテンツ化して対象化する。知識はそれをプログラムが扱うデータである。それをどう扱うかの知識であるプログラムもまた、データでありえる。 これは、プログラムをデータと同じメモリーに置き対象化して扱う、EDVACの内蔵プログラム方式という偉大な進歩である。しかし知識の対象化は、実処理を避け、 実務から管理へ無限後退し、どこまでも非効率になっていく。そしてそれによって、知識を引き出すのに時間がかかるなら、そのことを知らないのと同じである。


3.11 自動プログラミング

マシンの命令の並びがプログラムであり、それを人間の言語に近い形で記述する高級言語が作られた。"自動プログラム" という言葉は、コンピュータに高級言語 を実現する当初、使われた理想を表現する言葉であったが、それは決して自動プログラミングではなく、曖昧さをエラーとして除去した人間の言語に近い、マシン 記述言語を、マシンの命令の並びに変換するプログラムを作成することだった。最初、アセンブラという、マシンの1命令を1行に表し、命令をオペレーションコード (それを短い英文字で表すものをニーモニックという) とその引数としてオペランドで表される。それの並びには、プログラム上の場所を文字で表すラベルを置き、 ジャンプはラベルにジャンプできる。しかしそれでプログラムする処理は、余りにも煩雑で、注意を要し、人間的でない。それが人間がよく扱う数式に近い言語、 もっと書きやすい言語であれば、プログラムは容易になるだろう。しかし、プログラム言語は、マシン命令に変換できる言語から、マシン命令への変換を本質にする。 (C言語ソース xxx.c はASM言語 xxx.s へ変換され、その後ASM言語と1対1に対応する機械語列 xxx.o に変換される。cc -S xxx.c でASM言語ソースxxx.sを出力。 xxx.sも cc の入力になるが、asm xxx.s でよい。Cが行う最適化の結果をASM言語で確認できる。)

3.12 マシンの命令セット

マシンの命令セットをもっとよくすれば本質的に改善できるのかという問いがある。勿論、命令の種類の集合は、人間のデザインの結果である。まず、Intel の 8008 があって、次に 16 bit CPU の 8080 がでる頃、私は、RCAのCOSMAC の単純な命令セットを知り(1975年3月の雑誌、"電子科学")、このデザインがコンピュ ータの最重要設計だと思った。最初から汎用レジスタをもつRISCであった(*)。

命令は8bitであり、16種のオペコードIと16種の汎用レジスタを指定するNだけでできている。内部アーキテクチャは、8bitのアキュミュレータDと16bitの 汎用レジスタR(N)16個をもち、そのうちの1つR(P)がプログラムの流れを扱うプログラムカウンタである。Rを指定する4bitのレジスタにはP,X,Nがある。 Nは命令の下位4bitであり、Pはプログラムカウンタを決める。Xはそれ以外のもうひとつの指定レジスタで、I/OのメモリーM(R(X))を外部とやりとりし、 ALUのオペランドが、NによってM(R(P))かM(R(X))になる。割り込み時、XとPはTに退避し、P,X= 1,2 になる。Tはスタックに退避される。汎用レジスタが専用に されているのは、R(0)はDMA用、R(1)は割り込み用, R(2)は割り込み時のX用である。

普通のアーキテクチャは、少なくともプログラムカウンタは専用にもち、サブルーチンのためプログラムカウンタを退避するスタックのスタックポインタや、 割り込みのとき、状態レジスタやそれらを退避し回復するシーケンスを扱う仕組みがあればよい。計算をするには算術と論理の2項演算をするALUとその結果を いれるアキュミュレータは必要だが一時的に2項演算の何れかの値を保持するレジスタが少々あればよいと思われている。


何かの処理を記述するのに、マシン語やアセンブラでそれを行う時、マシンの使える命令セットをほとんど全て覚える必要がある。そういうとき、 覚えやすい構造(規則的なアーキテクチャ)と覚えやすい(規則的な命令セット)とで組みたい。それでないと、プログラマに余計な負担を与える。 例えば、メモリー上のある場所の値を取り出して、別の場所の値に加算するのに、アドレスを直接指定するマシンがなぜ不便かというと、 値の場所を表現することに命令の大半の部分を使用し、大半のメモリーサイクル時間を使用するという命令長の長さと実行時間の非効率を避けたいからである。

命令(オペレーションコード)をメモリーから読んで、命令の種類を解読し必要な処理を知るのに1回、これは全ての命令に共通に必要である。

命令のオペランドアドレス(第1のアドレス)をプログラムメモリーの流れの上から取り出すのにもう1回いる。ここで、命令のオペランドのアドレスでなく、 データを直接使用するイミーディエート命令であえば、それだけで値はとりだせるが、アドレスであれば、実効には、これをアドレスとして出し、メモリー から値を読み出す1回、少なくとも2+(1)回のメモリーアクセスを使って、値を得るのである。

加算される側の値の場所はメモリー上でなく、大抵、より高速な特別なレジスタとしてアキュムレータが用意される。そうでなければ、第2のアドレスの指定がいる。 その結果をどこか別の場所に仕舞うのであれば、その第3アドレスも必要になる。仕舞う動作は、結果をアキュミュレータに残し、それを仕舞うだけの命令に分離できる。

前半の処理は、アドレスをどこかのレジスタに置けば、そのレジスタに値を設定する処理は必要となるが、そこからのメモリー読み出しだけになる。 その場所が一定の場所でなく、移動していくとき、どこかの場所からのオフセットした場所を使用することができれば毎回場所を指定するよりよい。 メモリー上のテーブルの先頭場所をインデックスレジスタにいれ、オフセットを自動的に増加するような処理は非常に多く、 メモリーアクセスの効率化は重要である。アドレスも、一度指定すれば、何度も再利用することができるように、どこかのレジスタがアドレスを扱えばよい。

さらにいくつか値を置くだけのレジスタも欲しい。それらは、2項演算の1方であってもよいが、2項演算の1方の値の入ったメモリー上の場所でもよい。そういう データとアドレスを扱う汎用レジスタが考えられる。例えばテーブルの一方から読み出し、結果を別のテーブルに仕舞う場合、アドレスを表すレジスタは 少なくとも2本必要である。それらの命令セットの単純化とアーキテクチャを簡単化とは同方向の改良または最適化である。

しかし、一般に最も重要なこの問題は、産業の設計の成果であり、これが多くのプログラマの目の上のたんこぶ、孫悟空の輪っかであっても、 それの研究が余りに重要すぎ、重大な影響を与えるため、コンピュータ基礎論のテーマにはならなかった。昔の、IBM370はPDP11や今のARM7,9 とどう違っていて、 何が確実に進歩したのか。パソコンのCPUの8080, Z80, 8086, 186, 286, 386, 486, Pentium, II, III, Duoの流れ、それらを単に自動的変化や、 過去の無駄な変化と捉えることはできない。設計は理由があって変化し、それには誤りと試行錯誤もあったと思う。


3.13 マシンのアーキテクチャ

汎用レジスタはそのレジスタを8個〜32個のように多くしたものである。演算データは最初メモリー上の、プログラム領域又はデータ領域の何れかにあって(C言語 実行時はそう明確に分離されているようだ)、プログラム領域のイミーディエートデータ(プログラム中に書かれる定数)とデータ領域のデータへの直接アドレスが あればよい。データの値自体でなくアドレスで扱うのは数値を変数名で扱うような、代理表現、または代名詞だろう。汎用レジスタの指定が4bitですむことなどは データ値やそのアドレスの短縮表現と考えると理解できる。言語のなかに何度も表れるものは、代名詞に短縮記述される。アーキテクチャは、可能な内部処理の全 てを決めるが、命令セットはその内部処理の記述であり、それを短縮して記述できる方が理解しやすく覚えやすい。1命令の行う処理は、できるだけ要素に分解され て小さい方がそれらを組み合わせた複合命令をもつセットよりよいと思われるが、必要なものは備えないといけない。条件的にアキュミュレータからメモリー上の 値を引きそこに格納する、引き算命令だけをもつコンピュータで他のマシンを模擬でき完全、という議論は面白いが、理学的興味の議論で、その命令で書いたルー チンが別マシンの全命令に対応するような模擬の議論であろうから、命令セットだけの単純化は代わりにプログラムの記述が何100倍にもなり、工学的意味はないだ ろうと思う。

3.14 サブルーチン

計算機の最も基本的な要素は、演算と入出力だが、入出力はメモリーマップI/O(アドレスの1部分を入出力にあてる)でもよいから省略できる。サブルーチンは プログラムの複数記述されたルーチンの短縮機能である。ループでなく複数回行う処理は、毎回同じ事を書くよりもサブルーチンにしたほうが短く理解しやすい。 一方、サブルーチンコールとリターンのオバーヘッドはある。サブルーチンにも多重化があり、サブルーチンがまたサブルーチンを呼べるためには、状態やレジ スタを退避するスタックを積み上げ使う必要がある。高級言語がサブルーチンをサポートするとき、例えばCの関数がどこまでのレジスタを自由に使用し、どこ までのアーギュメントをマシンのレジスタを使い、残りをスタックに置くかなど、決め事がされる。

3.15 割り込み

割り込みは、外部要因の対応処理をできるだけ素早くするためにある。一定の処理が終われば外部を見に行く"プログラムI/O"ではなく割り込みなら(割り込み 許可の状態の)命令間にクロック単位で反応できる。割り込みは割り込み発生信号によって禁止され、割り込み処理全体で割り込みを禁止してもよいが、多重割 り込みを許す場合、割り込み発生時のCPU状態を回復するためにと割り込み処理で使われる内部レジスタの退避が終わると割り込み解除命令を使う。さらに割り 込み信号に割り込み優先順序を与え、未処理の割り込みは、待ち行列として扱われなくてはいけない。割り込みは、多数の独立した処理を並列的ハードウェアを 単独のCPUが行う。外部ハードがあればそれで済む独立処理を扱う割り込みは、最初からマルチCPUなら不要だが、独立ではなく相互作用する処理間の柔軟性が 可能になる。しかし、その間の通信には注意がいる。割り込みプログラムが行った処理を、割り込まれたメイン側の命令によって帳消しにされることを避ける 必要がある。あるプログラムはあるアドレスに値を書き込むが、その直前に割り込みプログラムがその同じ場所に書き込んだ値は、メイン側によって上書きされ 無視される、ようなセマフォーの問題が起きる。割り込みが急いで書いた値が、メイン側で読み取るのが1巡遅れになって、処理個数が1つ減るようなことが起きる。


3.16 COSMACのアーキテクチャ

多くの8bitマシン(8008,6502,6800,COSMAC,F8,SCAMP)が登場した頃、それぞれ特徴のあるマシンの構造が見えたが、COSMAC のアーキテクチャに驚いたのは、 プログラムカウンタを専用にせず、汎用レジスタのどれもがプログラムカウンタになれることで、プログラムの流れの下にデータの流れ(並び)を置き、別の用意した ルーチンがそれらを処理をして、データの流れを終えると通常のプログラムの流れに戻るようなことができる。まるでそれは、多次元データのイミーディエート ロードである。また、複数のプログラムの並列の流れを用意しておき、タイマー割り込みによってそれらを切り替えるタイムシェアリングが可能である。個々の プログラムは何も他の影響を受けずに自分の処理を中断続行できる。また、複数のプログラムが互いに処理を終えると別の流れに処理を譲ることができる、それら メインでもなく、サブでもない、ルーチン間はコルーチン(協同ルーチン)というOSに必要な高度な概念が構造的に用意されていることである。勿論、汎用レジスタ の退避は自動化されないし、サブルーチンコールでさえ、ハードウェアを用意しない。

3.17 汎用レジスタ

PDP-11のようなミニコンの汎用レジスタと演算のオペランドには変種が考えられ、演算命令の1オペランド型、2オペランド型、3オペランド型がある。1オペランドは、 2項演算の片側はアキュミュレータで他方を指定する。2オペランドは2項演算の両方を指定か、結果格納指定。3オペランドは、2項と結果の格納場所を指定し、 アキュミュレータはない。さらにそれら3種に、それぞれレジスタ間接アドレスがある。いずれが効率的かは判定されない。それらが実製品の仕様であるから。 それ以前には演算の値のアドレスを記述する1〜3アドレスもあったが、それらよりも、汎用レジスタの指定では短く書ける。

例えば1オペランド型アドレスでは、ある値をアキュミュレータに加算するのに、プログラムからアドレスを得て、それをデータのメモリーをアクセスする2回(アドレス が16bitでデータが8bitのようにアドレスとデータのbit幅が倍なら3回)のメモリーアクセスを必要とする。2,3オペランドではもっと複雑である。それが汎用レジスタ からのアドレス(又はデータ)なら、実行サイクルは1(又は0)メモリーアクセスですむ。アーキテクチャによる実行の効率は、高級言語のコンパイラや、アセンブラに 隠されるから、余り考慮されなかったのだろうか。

(Wikipediaによると、1970年代〜80年代の16bitミニコンPDP-11は、I/O専用バスなしのUnibusでI/O命令なし、8本の16bit汎用レジスタR6がスタックポインタSP, R7がプログラムカウンタPCだった。64kバイト空間のなかRnとポインタ操作(前減少/後増加)を伴う8アドレスモード(Rn, *Rn, *Rn++, **Rn++, *(--Rn), **(--Rn), 次ワードをインデックスXとし *(Rn+X), **(Rn+X))と演算種は"直交"していた。PCがプログラムに見え相対アドレスを使用した位置独立な実行ができた。 VAX(32bit)がこれを継いだ。

後の典型的なRISCマシン、MIPS社のR3000(32bit20MHz-40MHz)(1988)は、3オペランドレジスタ間演算又は2オペランド+16bitイミーデェートで、32本の32bit汎用 レジスタ中にスタックポインタSP(29)、リターンアドレスRA(31)があるが、プログラムカウンタPCは別であった。ARM(Advanced RISC Machines)アーキテクチャの ARM-7TDMI(1994)も3オペランドレジスタ間演算、16本の32bit汎用レジスタの13=SP,14=RA,15=PC。実行サイクルは1で、殆ど全命令に条件が付き、演算前のシフト が付いた。RISC(縮小命令セットコンピュータ)とは、汎用レジスタを多数もち、メモリーはload/store だけで、演算はレジスタ間のみ、を意味するようだ。 その反対語CISCは、68000系や286,386,486,Pentiumなどx86系を指した。)

3.18 命令セット

COSMACの命令はI(4bit)とN(4bit)に分かれ、Iで16種の命令をもつ(下表参照)。COSMAC は16個の16bit汎用レジスタをもつ。P(4bit)の指定する汎用レジスタR(P)が プログラムカウンタである。Pの値を変えるとプログラムカウンタが切り変わりプログラムの流れがジャンプする。Xが指定する汎用レジスタのR(X)は、ALUのオペランドや、 I/Oの位置を示すアドレスとして使われる。ALU命令がNによってALU演算種類を指定し、X又はPによって汎用レジスタを指定する。1オペランド型のメモリーM(R(X)) 又はM(R(P))である。M(R(P)++)はイミーディエート値のロードになる。サブルーチンコールは、例えば、P=3 から P=4 にするとコールルーチンへブランチ&リンクし、 P(3)に続くイミーディエート値(サブルーチンアドレス)を取り出し、R(3)をスタックに退避し、R(3)に設定し先頭に戻り、P=3にする。これでサブルーチンが呼ばれる。 リターン用のルーチン P=5 は、退避したR(3)をスタックから回復して先頭に戻りP=3にする。

3.19 メモリーアドレスモード

メモリーアドレスモードは、レジスタ間接アドレスだけであることが、とても普通でないから批判された。1,2 のインクリメント、ディクリメントには違和感を 感ずる。それがそれほど必要なのだろうか。相対アドレスを使えるマシンもあるのになぜ増減だけなのか。4,5 はメモリーからそのままLOADは、ALU演算に含める ことができそうだが、そのときNでなくP,Xになる。6 のI/O 命令はなくてもよいと思うだろう。あるアドレスをI/Oに使用するメモリーマップI/O は一般的である。 ところが、COSMACは、メモリーとI/Oデバイスの間の転送であり、普通はCPUを介さないDMAと同じ発想のI/Oである。メモリーから外部装置の間を仲介する。CPUが ビットマップ表示用DMAコントローラを含んでいる。


COSMAC の命令セット I(4bit) with N(4bit)	D(8bit)		意味
-------------------------------------------------------------------------------------------
0: Idle						Nop
1: Increment R(N) by 1				R(N)++ 
2: Decrement R(N) by 1				R(N)-- 
3: Branch					if(cond(N)) R0(P)= M(R(P)++) else R(P)++ 
4: Load  D from M(R(N)) and Increment R(N)	D= M(R(N)++) 
5: Store D in M(R(N))				M(R(N))= D 
6: Input output byte transfer			Output= M(R(X)++), M(R(X))= Input 
7: Interrupt control				if(N==8) M(R(X))= T, if(N==0) T= M(R(X)++)
8: Transfer R0(N) to D				D= R0(N) 
9: Transfer R1(N) to D				D= R1(N) 
A: Transfer D to R0(N)				R0(N)= D 
B: Transfer D to R1(N)				R1(N)= D 
C: Transfer D0 to R00(N)			R00(N)= D0
D: set P to value in N				P= N
E: set X to value in N				X= N
F: ALU operation				D= ALU(N)(D,M(R(PorX))) 

3.20 16bit 化

もし、COSMAC のアキュミュレータDを16bitにして汎用レジスタサイズと同じにすると、8,9とA,Bはそれぞれ1命令になり、Cは不要だろう。命令8bitの残りの8bitが空く。 命令幅が大きくなれば、当時の8bitマシンなど何の示唆を与えるものだろうかとも思う。汎用レジスタがアドレスなら現在は、最低限32bitであるし、演算も32bitは 最低限必要だろう。現在は、1クロックで浮動小数演算を複数行う。しかし、コンピュータの構造に親しく完全に理解でき、それについて真剣に考えた時期があった。 COSMACでコンピュータを作って音声認識の実験を行っただけでなく、COSMACに類似のCPUをもつコンピュータをTTLのMSIを組み合わせて作ったことは、理想のアーキテ クチャと命令セットを手にいれるためである。もはや、命令セットの問題を我々は完全に忘れているが、アーキテクチャと命令セット問題は宿題である。当時、必要な 16bit拡張だけを追加した8086を受け入れた人々は、私がなぜそれを問題にするか理解できなかっただろう。当時、16bit演算を入れることの方が能力的には断然有効 だったのであるから。

16 bitマシンの命令セット I(4bit)+N(4bit)+L(8bit)	D(16bit)	意味
--------------------------------------------------------------------------------------------
0: Idle
1: modify R(N)				R(N)+= L; (L= -128,..127)
2:
3:
4: load D from M(R(N)++)		D= M(R(N)++);
5: Store D in M(R(N))			M(R(N))= D;
6: Input output word transfer		N=0: M(R(X))= Input; N=8: Output= M(R(X)++);
7: Interrupt control			if(N==8) M(R(X))= T; if(N==0) T= M(R(X)++);
8: Transfer R(N) to D			D= R(N);
9: Transfer D to R(N)			R(N)= D;
A: Arithmetic load D from M(R(X)++)	D= Arith(M(R(X)++),D);
B: Branch				if(cond(N)) R(P)= M(R(P)); else R(P)++;
C: Arithmetic Transfer R(X) to D	D= Arith(R(X),D);
D: set P to value in N			P=   N;
E: set X to value in N			X=   N;
F:

L	A:		C:
--------------------------------------------
0: D=  M(R(N)++)	D=  R(N)	LDA
1: D+= M(R(N)++)	D+= R(N)	ADD
2: D+= M(R(N)++)+Carry	D+= R(N)+Carry	ADC
3: D-= M(R(N)++)	D-= R(N)	SUB
4: D-= M(R(N)++)-Borrow	D-= R(N)-Borrow SBB
5: D|= M(R(N)++)	D|= R(N)	OR
6: D*= M(R(N)++)	D*= R(N)	AND
7: D^= M(R(N)++)	D^= R(N)	EXR

3.21 演算モード

4:のLoadにALUを通すとALU命令F:は不要。Rレジスタ指定が X,PでなくNにできる。演算指定は、Lか、別のレジスタA(退避がいる)が考えられる。 汎用レジスタをアドレスに使ったメモリーとDの間の演算。8:,A:の転送命令でRはデータバスと連結があるので、Rを演算にも関与させたい。D+= R(N);型の演算は、 transferに修飾して可能。逆の R(N)+= D; のような演算は、R(N)のRead/Write両方を要し、実行サイクル以外を1クロック動作させるのはD-ffなら可能だが、 レジスタファイルでは難しいかもしれない。この方が使いやすいかは、次の2つを比較すると、そうでもないようだ。汎用レジスタを複数アキュミュレータに するのは、2オペランド型が向くが、R(N)+= M(R(X)); Rレジスタ指定が2つは2サイクルいるので考えない。さらに、M(R(N))+= D; はメモリーサイクルが2ついる ので考えない。
D+= M(R(N)++);
D+= M(R(N)++);
R(N)= D;

D= M(R(N)++);
R(N)+= D;
D= M(R(N)++);
R(N)+= D;

R(4)+= M(R(3)++);
R(6)+= M(R(5)++);

3.22 フェッチサイクルの隠蔽=ハーバードアーキテクチャ

しかし、実行サイクルが1クロックというのは、フェッチサイクルが1クロックあるので、必ず1/2の速度効率である。実行サイクルを複数にすることは、多少の 制御ロジック追加によって、命令フェッチ比率を減らし実行サイクル比率を増加する。実行サイクルが複数nサイクルなら1/2を超える効率 n/(n+1)になる。 しかし、複数の実行サイクルをもつ複合命令は、それをマイクロ命令に分解できる。そして、物事は単純な方がよいし、命令セットが単純で覚えやすくなるが、 必要な命令数のメモリーが増え、命令フェッチ時間が損失になる。この矛盾は、命令バスと実行バスを分離し、フェッチと実行サイクル数ともに1にし、1/2の 効率低下を命令フェッチと実行の重複によって防ぐ、私もそのファームウェア作りをしたARM社のARM9のような組み込みCPU用、ハーバード・アーキテクチャが 解決した。但し、それによってプログラムをデータと同列に扱うEDVAC以来のノイマン型の利点を失う。このマシン上でコンパイラやアセンブラは動作しない。

3.23 理解しやすい構造と命令

アセンブラ等のコンピュータ用言語が作られた理由は、マシンの不都合さ(醜さ、覚えにくさ、不可解さ、使いづらさ)を隠すためと考えられるが、アセンブラ はマシンの特徴を十分隠さない。プログラマは、使いづらい命令セットでも完全に覚えなくては書けない。そして、命令セットの良否は、マシンの実行時の 効率(速度)上の差が出ると考えられる。マシンの設計とは、アーキテクチャと命令セットの設計が基本である。

使用目的や、命令セットによって命令の使用頻度が違わないとすれば、アーキテクチャを決定すれば、命令セットは命令を記述する符号として、命令の実際の 使用頻度に合わせて、長さの違う命令を再設計すればよい。命令セットの圧縮は、命令のVLC化で可能だが人間の認識しやすさを犠牲にする。通常のアセンブラ の必要なビット数は、数bit程度だろう。しかし、問題はそこにはなく、私がこれを実際に作ったように、COSMAC がアセンブラなしに機械語を16進キーと16進デ ィスプレイがあれば、直接打ち込めることである。


3.23 命令セットのデザインは意味がないか

しかし、そういう命令セットのデザインは、本質的でないという否定を私は受けた。コンピュータ基礎論からいえば、引き算をして条件付きジャンプする命令だけ で全ての処理はできるという。これは演算とジャンプとを組み合わせた命令であり、要素的命令でない。命令コードは同じでも、そのアーギュメント(命令の引数)は 個々に違える必要があって、命令列全体をなくせるわけではない。大半の命令で不要な要素をもつ命令に統一する意味はない。しかもそのために何倍もの非効率を 生むだろう。さらに、コンピュータ基礎論ではどれほど効率的であるか非効率かに関わらず、全てのメモリーを内蔵する計算機は、メモリーをチューリングマシン のテープとみなせば、チューリングマシンのできることしかできないという能力限界があるということが知られている。チューリングマシンは、"万能" である。 それは、他のマシンをシミュレート(模擬)できる意味である。それが強力で高速であるという意味は全くない。ひとつのチューリングマシンは他のチューリングマシン の記述をして、それの動作を何100倍も遅くなっても、同等の模擬ができることをいうだけである。それだけで、他のどのマシンでも可能な計算結果を出す能力が あると考えるのである。

3.23 チューリングマシンは万能

組み合わせ論理回路は、入力の全ての場合に対して、出力を記述した真理値表を書くことで完全に記述でき論理素子で制作できる。(出力が1になる真理値表の各行の1であ る入力に対してANDを組みORする。) それに対して有限オートマトンは、入力と内部状態に対して、出力と次の状態が定義されたマシンで、内部状態をもつマシンを全て 記述できる。チューリングマシンはそれにテープとヘッドの左右移動が付いただけである。テープとヘッドと内部状態をもち、テープ上の入力文字と内部状態に対して、 テープに書く出力文字と次の内部状態とヘッドを左右に動かす動作か停止だけを定義する。最初、チューリングマシンの説明を受けた時、なぜ、愚かなそれの動作を詳し く説明するのかと思った。速度の議論が全くない。

3.23 チューリングマシンをハードウエアで作るのは無知

チューリングマシンは、理解しがたいので制作すべきでないか? 制作して数1000万回、数億回のクロックでどういう結果になったと実証すべきで、誰が思考によって全 ての動作を辿り、完全に正しい結果を説明できるか? いや、これは現実の計算の道具ではなく、単純な動作が理解容易さを保証した論証の道具である。そして、この問 は本質を理解しない無知な質問とされる。チューリングマシンは、他の計算機械より高速なはずはない。テープはメモリーであり、テープを右に行ったり左に帰ったり するシーケンシャルアクセスであり、ランダムアクセスメモリーを無限(?十分?)にもつ方が速いだろうし、人に明確だろう。しかも数値は単進数という1をそれだけの数 並べる数値表現である。位取り記数法でNbitは、単進数では2のN乗の大きさになる。その数値を乗り越えるには2のN乗のクロックがかかる悲惨さだ。しかし、 数値の連結には2数の間の区切りを消すだけだから、行って帰る必要もないかもしれない。では2進数加算はどうか。何クロックで何bit長の加算ができるか? 現行マシンは、チューリングマシンにどこが勝ってどこが負けているか、そういう議論に答えがない。なぜか。それは動作が決して理解容易でないからである。 テープ上の文字を探して行ったり、マーカーを書いて来たりの動作が理解しやすくはない。2進数の数値を並べて、同じ桁の位置に来れば、全加算器full-adderの 結果の文字を出すだろうが、2数値の間を行ったり来たりするのは単に数値を切り替えるだけの目的である。それはテープが無制限にあるから、数値の桁数制限は ないが、bit数Nだけの移動がN回は繰り返され N^2回のクロックがかかる悲惨さである。


3.24 停止問題とは何か

それは確実に現行マシンが勝っているからではなく、全てのマシンの同等をいうからであり、これが全ての現行マシンのデザインの意味の否定だからである。 チューリングマシン停止問題(ある問題でチューリングマシンが有限の時間で停止しないことが、別のチューリングマシンで模擬して有限の時間で分かり停止する という仮定は矛盾するという話だった。このことをバグはバグと判定できないと理解する人もいる。しかし、なぜそこに無限を持ち出すのか?)や、NP完全(Pは 多項式時間、NPは多項式時間より大きい時間を要する問題。セールスマン巡回問題はナップザック問題と同様にNPであろう、全てのNPは同じクラスと予想する話。) の話をするコンピュータ基礎論は、コンピュータにとって通常、最重要である実行速度や、メモリー容量、計算効率を考察の対象にしなかった。 具体的なそれらを対象にしてコンピュータの性能を例えば2倍にすることは世界中で毎年のように行われた。それがLSIの速度向上だけによって極端にいえば、 15年で1000倍、30年で100万倍の速度向上が行われ、古いマシンを無意味にするなかで、この議論は速度向上では扱えなかった深い問題の存在を明らかにしたが、 それがどうすれば解けるという話ではなく、どのマシンなら解けるという話でもない。現実の時代要求に関係しない。全ての機械が同等ならコンピュータ科学と 何の関係があろう。確率的マシンなら解けるなどという議論は全く、確率的にしか信用できない。コンピュータ基礎論は名前の割に高踏、浮世離れで、 理学であっても、工学ではない。

3.25 コンピュータの問題はどう解決されたか

現実のコンピュータの直面する問題を見ない学者がよいマシンを設計できるはずがない。元来、彼らは現行のコンピュータを改善するつもりもないし、コンピュータ に触れてもいけないといったという学者もいた。これはデザインセンスというものを全く失った状態である。物を設計するにはその物に近いものを使うものである。 シミュレータを作って評価して、デザインを改良するものである。この理論家にいうべき言葉は、物作りに理学とは大層ご立派なことですね、である。勿論、私も2進 数の加減算を組める程度に必要な論理設計の一応の勉強をしたつもりだが、論理設計が次の展開を見せる兆しを見出した思いはなかった。理解したらそれっきり意味 を失うような、完成した科学は確かに面白くない。しかし、当時から何10年経っても役立つコンピュータは、ただ速いだけの単独マシンであり、並列マシンや、その 他のマシンはどれだけ世話をしてもその仕甲斐のない役立たずであった。並列マシンは数個の並列ではなく、数千万、数億のレベルになって意味が出てくるだろう。 この数十年間、速度向上しか実質的な意味がないように見えることと、速度を全く評価しない計算科学というのは、計算機械の研究に一種、悲劇的状況といえるのではないか。


3.26 コンピュータの速度は十分だったか

PCが昔の大型やワークステーションを明らかに超えて高速になり、携帯電話がスマートフォンになって、動画を扱うことができるタブレットPCが身近になって、我々は、 素子の高機能化と高速化にどれだけ頼ってきたかを知るのである。恐らく、OSは大して違わない。動作速度は、必要な動作を満たせばそれ以上はいらない。今も昔もそう 思われているが、昔、私は8008,COSMACやAPPLE-II(1MHz 6502)を大事に使った。Z80 3.58MHzのMSX2の時代(1980年代半ば)で開発の責任者は、CPUにこれ以上の速度は 要らないと言っていた。その後MSXは数年で廃止された。100MHzのとき400MHzのPentiumはありえないと思うGCT上司がいた。数GHzのクロックというのは不可能なギャグだった。 高速CPU用のソフトの規模は当時の道具(アセンブラ)では開発も難しく、時代はどのような特別な立場の人をも追い抜き、我々には先が見えなかった。数MHzとの1000倍の 恐ろしいギャップは時代以外には越えられなかった。計算機は計算だけではなく通信にもインターネットの自他特定と正確さのために必要だった。そして、 動画の圧縮解凍表示にも音声認識にも大量の計算処理が必要であった。現在、2003年頃からのCPU速度向上の停滞を問題と思わない人が多いが、それこそ近視眼的である。 そうでなければ今頃、コンピュータは現実の1000倍速度向上し、数THzをもつ社会に何が可能になっているかを私は想像したい。

3.27 再度、自動プログラミング

高級言語でのプログラムという意味でない"自動プログラム" という言葉の auto は自動の意味とともに自己の意味で、自己プログラミングでないといけない。 コンピュータが自己をプログラムし直す必要がある。 それをしないで、知識がコンテンツ(データベース)としてある状況は、インタープリタとコンパイラの違いに相当して、まだ間接的で何100倍も遅いものだろう。しかし まず、インタープリタの形で人工知能は動作し始め、知識は世界中のインターネットの知識検索から得る体系であり、主語述語の構文と自然言語を理解し、自主的な文章 作成と発話を行い、他と議論を行うことから始まるのは現在の人工知能の外的形態が望まれる最終形態を持っている(似せている?)ことは確かであり、これに対し、その 達成度に不満はあっても、我々は肯定的であるべきである。

3.28 汎用の問題解決ツール

例えば、私は処理系に触っていないが本で読んだ、直接にベクトルや行列を扱う言語 A Programing Language(APL)というものがあった。それは言語が扱う解釈実行の 単位が大きいからインタープリタでも速度低下が小さい。複数の実数の並びを変数として加減算はベクトル加減算、乗算はベクトルのスカラー倍、内積、外積、直積を 扱い、除算は何だったか。行列には加減算、乗算、除算(逆行列)がある。そのような扱いが難しい複合算術も、高級言語が対応すればよい。その数学に親しくない理由は それを扱う道具がないからである。ではテンソルはどうするのか、テンソルを扱う言語があってもよいし、すでにあるかもしれない。専用のライブラリ関数を用意するだけ でも教育に有益であるが、それは人間が不得手な難しいプログラミングを避けるだけであり、人間が物理において必要を感じ作り上げた完成した数学専用の補助である。 その数学によって理解できない問題に直面するとき役立つわけではない。どのような問題にも人間は対処してきたように汎用の問題解決ツールが"人工知能"であり、 それがどのようなアーキテクチャで可能か、ぼんやりと予想する。必要なのは神経回路網であり、脳の延長である。それは賢い執事でなく賢い秀才の大量複製だから そのとき残された生き方は何か。代理を育てるとき人は何をするかという問は昔から教育の矛盾だが相手は人間でなく機械である。機械は知識の修得にヒトのように 数10年を要しはしないだろう。最初の1秒で世界の知識を得てしゃべり始めるだろう。そしてそれは十分、誰にとっても話し相手になる存在だろう。


≪=BACK TOP∧ NEXT=≫

4. ステレオ分析

普段あまり考えない単純な問題。複数センサーを使った位置検出。

光でも音でもよいが、一定強度の信号を発する点信号源が1つ、それを受けるセンサーが1つあるとき、これが示す立体情報は何かといえば、距離である。 センサーの受ける強度は距離の関数を通している。相手の強度が分かっているとき、その距離の関数が距離の2乗に反比例なら、距離が分かる。 それは、空間内で信号源のある球面が分かることである。

それでは次に、場所が固定したセンサーが2つで得られる情報は何かというと、両センサーからの距離が分かれば、センサーを結ぶ線分を含む軸対称の 面内の位置が分かる。距離の差から、x方向が分かり、距離の和から両方のセンサーを焦点にする楕円の径が分かるといってもよい。 2点からの距離が分かれば、ある平面内の位置が分かり、空間内ではその平面を軸で回転させた円が分かる。

それは結構な情報であるが、実際は、光でも音でも、2つのセンサー、マイクからのステレオ信号で音源位置を特定するのは難しい。それは、強度には 角度による関数がさらに掛かるからである。信号源からの信号は無指向性でなく、角度関数が球状でない。受信側にも角度関数があり、送信側と受信側、 両方に角度関数がある。さらに、信号源の強度は通常、未知であるか、変動する。

信号源の強度Mが分からないとき、受信した強度 M/r^2 では距離rが分からないが、ふたつのセンサー間には相対的な強度がある。距離の2乗に反比例した後、 2センサーの和差積商の計算ができる。それによって何か位置が出るのだろうか。和一定、差一定、積一定、比一定は、センサーを含む平面上で曲線 (空間上では曲面)となる。距離の和一定は、焦点からの距離の和が一定の楕円だが、センサーでの差一定は何か、比一定は何か、を次に考える。


源の強度が不明(=積ノイズ)でも2センサーの相対強度(比)は変化しない。源の強度をM、距離2乗に反比例とし、2センサーまでの距離を r1, r2 とすると 源から影響は M/r1^2, M/r2^2 である。両者の比aは、a=(r2/r1)^2、距離の逆比の2乗である。

センサー座標を(-1,0),(1,0)とし、信号源の座標を(x,z)とすれば、各センサーまでの距離の2乗は、r1^2= z^2+(x+1)^2, r2^2= z^2+(x-1)^2 である。 距離の2乗の比を使い、a{z^2 + (x+1)^2} = z^2 + (x-1)^2 から、2ax= (z^2+x^2+1)(1-a) もし a=1 なら、解は x= 0 の直線で、a!= 1のとき、 z^2 + x^2 - 2bx + 1 = 0, b= a/(1-a)、から z^2 + (x - b)^2 = b^2 - 1、中心(b,0)半径 √(b^2-1)の円。比が一定の上記平面内の位置は円(又は直線)、 空間内では球面(又は平面)になる。

距離の差は、r1 - r2 = 4x/(r1 + r2) = 2x/(z^2 + x^2 + 1) だから、分母一定と近似出来るとき x比例だが、分母はy=1の原点からの距離の2乗であり、 距離の差一定は、近傍では線分に垂直な平行線に近いが、x,zが1より大きい遠方では放射状に広がる。

距離の2乗の差は、r1^2 - r1^2= 4x であり、正確にx方向成分だけが出る。

センサー受信強度の差は、距離の2乗の逆数の差であり、M/r1^2 -M/r2^2 = M(r2^2 - r1^2)/(r1^2 r2^2) = 4xM/(r1^2 r2^2) つまり、Mと分母とを一定と みなせるとき xに比例する。分母は、zやxの絶対値が1より大きければ変化しない。すなわち、強い光源の2センサの差信号は xに比例するが、 分母はrの4乗であり、rの-3乗比例の小ささになる。

3つ目のセンサーを使えば、2次元座標を与える。私は昔、ディスプレイに視点の位置を反映させるために3つの光センサーを試そうとした。受信強度比だけでなく、 伝播速度を利用した時間遅れを使う方向測定も併用でき、単発信号でなく連続信号でも相互相関関数分析を使い、LIGOの重力波検出ではこの方法が使われた。


≪=BACK TOP∧ NEXT=≫

5. SNRについて

多くの画像符号化の評価指標のなかで、最も意識されるものは、SNR(信号雑音比)である。符号化を仕事にして、これを意識しない人はいない。 SNRに反映しないような画質の良非もある。例えば、画素が1画素横にずれただけで、SNRは致命的になるが、人は気が付かないことが多い。静止画では全く 検知できないだろう。だから、動画符号化で、SNRに反映しないような符号化を志向する人は、殆ど、違う世界の符号化をやる人であろう。

現代的な良質を求める符号化は、SNRで完全に評価できる。1dBの違いは誰が見ても判定でき、0.3dBはよく見れば判定できる。0.1dB以下は誰が見ても分からない。 極端に低レートの符号化、モデルベースなどでは、それは当たらないだろう。原画と一致しなくても綺麗に感ずる画像を出せばよい。しかし、そのような 手法は、波形符号化と言われる現代の符号化では採用されなかった。

そして、高域の再現性は、SNRが上がらないことがある。符号化は、低域通過フィルタになりやすい。高域は符号量を使う割に、SNRの向上が少ない。 量子化マトリックスはこれをかなりの程度改善した。高域の量子化を粗くすることが、必要な高域の再現を良くしたという逆説的なことが起きた。 しかし、一般的に、成功した道具は、SNRを向上させる。符号量を増やせば、SNRは確実に向上する。むしろそうでない場合は、大変な問題である。 符号量の増大と、画質の向上は単調な関係にあって、それを使って、この道具は、10%の符号量増大に相当するというという宣伝文句をいうことがある。

10%は大きくて、かなりの人がこれを検知できる。大半の道具立ては、それ以下である。5%の符号量増大に相当する道具はまれで、多くの道具立ての組み合わせ でできた符号化の仕組みは、本当にそれが必要かどうかという疑いをもってみることが必要なものである。少なくとも1つの道具を導入することは符号化の機械 を複雑にして単純であることを捨てているからである。動画像の道具立てを全て信用するようにいうことはできない。それは、ある研究者の又はある会社の 特許に関係した利益を保持するためだけにあるのかもしれない。


私についていえば、SNRしか信用しなかった経験がある。同じ会社の研究者内でも、それは私の特徴でもあった。数値に基づかないいい加減な確認できないこと をいうことを避けるという意味で、数値で表現できる画質はそれしかなかったからである。但し、画像によって余りにも違うSNRでなくとも、というのは、画像 シーケンスが異なるだけで同じビットレートでSNRは大きく変わる。画像によって、必要なビットレートが違うということかもしれないが、余りにもSNRは画像に 依存する。だから、標準画像の10種全てを使って示さないと道具の効果は示せなかった。

SNRは、画像の信号のSではなく、最大信号である255に対する対数差であり、本当の信号パワーを扱うことができないから、そうなっている。しかし、SNRは、 平均2乗誤差であり、どうして誤差の2乗の平均であるかは、疑問がある。平均絶対値誤差で何が問題だろうか。平均2乗誤差は、たまに存在する大きな画素値 の差を大きく評価し、多くの画素に分散した誤差を小さく評価する。直流の誤差はSNRでは小さく評価されるが、低域、直流の誤差ほど大きく見えるものである。 そして、SNRなりの目的は平均誤差をいうためのものである。誤差に重みづけられた平均誤差ではない。

しかし、いま、このことを思い出すのは、SNRを否定する符号化の人は、何か説明できない効果を利用する符号化をいうためか、自分の評価をされないために やっていたと考えられるからである。全く結果を出せない場合も、数値評価でない場合は、否定されないのである。


(*)今思えば、それは「コンピュータ基礎論」の最重要事である。当時は、それを研究するのはコンピュータ会社であり、大学の研究ではない、それら商用製品の優劣 比較は、遠慮すべきことだった。当時、VAX11-780が1MIPSという、dryston はあったが、まだベンチマークプログラムによる実行時間比較など存在しなかった。高級言語 C言語があれば比較できたはずであるがまだ手元になかったし、私は大学にアセンブラさえ購入してもらう気はなかった。コンピュータ基礎論は、現実のコンピュータ のアーキテクチャや命令セットは、複雑すぎて/具体的すぎて/新しすぎて/実際何も知らず/興味がなかったか、最終的に関係しなかった。そのくせ、コンピュータを 教えるには、仮想的なマシンを利用して直接アドレス、間接アドレス、イミーディエート値、算術論理演算、メモリー、キャッシュメモリ、SIMD命令、投機的命令読出、 並列マシンの共有メモリ、シストリックアレイなどの具体的アーキテクチャを教える本を教科書にしていた。なぜ、それらの命令やアクセス方法が必要なのか、実際に どれだけ使われるのか、実用にはそれが必要な知識だった。その後も1985年に私の入社したASCIIシステム部ではCOSMACを柳下のドジョウと言い、そのアーキテクチャを 薦めても誰も喜ばない。APPLE-IIから6502を知る人もいたが、インテルの8080,Z80のアセンブラプログラムを会社で仕事として使い、MSX、MSX-DOS などを開発し、MSX のライセンス商売が続くと思っていたのであるから、80系のマシンを脅かすアーキテクチャは、80系のソフト資産の会社には最も受け入れ難かった。CPUは、16bit,32bit に延びてから1995年頃もう一度、個人PCに戻ってきた。AMDが先に1GHzを達成し、数GHzのとき、DualCoreになり、まだインテルはペン4から、Core 2 Duoの時代(2006)の前、 2001年頃、ICチップに組み込み型CPUのARM社のARMアーキテクチャが汎用レジスタ、1命令1クロックの点でCOSMACに近いことを知った。COSMACの汎用レジスタのなかに Pレジスタが指すプログラムカウンタというアーキテクチャは、命令とデータを分離しない方向であるが、完全に時間的分離が必要で、バス効率50%であり、どのCISCよりも 低い。ハーバード・アーキテクチャは、命令バス32ビットをデータバス32ビットと分離し、フェッチを実行サイクルと重ね、バス効率100%を実現した。実行サイクルの 単純化がバス効率を下げるという矛盾を完全に解決した。汎用レジスタの中にプログラムカウンタが指定できて切り替えられる構造は、COSMACだけである。(2018/9/2,7)