目次
1. 光線追跡法
2. 知識と価値
3. プログラミング
4. ステレオ分析
5. SNRについて
光線追跡法という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-p0)×v|^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= b・v
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種のディザの切り替えでなく、 元のソフトにして欲しいといってきた。両方とも、私は断った。そうしたければ、自分で直して欲しい。私は、いまそれをする時間も無(ければ、 その義務もない。さらにそうしたほうが良いと決して思わな)いから。
大人になって、会社での仕事においても、辛辣な言い方であるが、仕事を理解しそれに十分に対処しない人、言語でプログラムを書けない人、 毎日長時間のプログラムとデバックができない人は、安易に結果を求め、完成品の技術を入手しようとする。 出向者の集まりの会社で出向元に出す特許を、私から無理やり聞きだした知識で特許申請した同僚、これは許しようがない。 その会社からはそのような人が来ていた。何もないところからプログラムを作成することを "from scratch" スクラッチ(引っ掻き)から プログラムを書くという。プログラムは「ひっかき」から始まるのである。ぼんやりした概念でスタートして、まず計算機言語で表してみて、 それによって、プログラムが備えるべき必要な機能を知り、望む結果でないときは、自分のバグか、自然のモデリングの誤りか、相手の使用法、 操作ミスかを判別しないといけない。仕事上のプログラムで自分のミスのときはバグフィックス(修正)しないといけない。
プログラムを作り、デバッグし、シミュレーションを繰り返す地道な技術的な作業(=エンジニアリング)が必要なところに、出向から社に戻る時期に 同じ社内だからMPEG-2提案に使ったCプログラムのソースをくれるのが当然と公然と主張した人たちの愚かな図々しさには閉口した。彼らは私と同じ 期間に同じ会社にいて何をしていたのか。彼らは技術者になれなかったのに、技術者としての成果を持ち帰りたいのである。それを与えたとしても、 彼らはそれを受け継ぐこともできないだろう。彼らの社内には本物の技術者がいて、それに威張って成果を渡せば社内を騙せるとでも思っているのか。
同じようなことが、MPEG-2提案に共同研究をしたW大学にあった。まずソースを欲しがり、(後の別のS大学との話だが、渡すと学生がソースの中の 名前をまず変更しているのを私は見た。それはすべきでない。そしてソースを購入した訳でもないのに、ソースの説明を求め、バグを発見して も修正をなぜか私に求める...ソースを読んで理解し、バグなら修正しようという努力をしない。)、私が符号化効率を追求、相手は階層符号化を 追求するので、その時点から別々の提案にすることにした。ソースは一部は利用できるだろう。協同作業は殆どなくなったが、会合しても、非イントラ は逆量子化に中間値を返すから量子化に丸めが要らないことを説明しても決して理解しようとせず、従わず、私がフィールド間予測に使った 画素中間位置の内挿フィルタに[-1,5,5,-1]がよいといったことを、階層画像の縮小フィルタに用い(それでは通過帯域が広すぎる)、最後にはその 協調を信用せず、私がボケボケの結果を仕組んだと疑ったという。なぜそういうことになるのか。プログラムを少し修正して確認すれば分かること であるのに、すぐに画像で確認ができなかったら、SNRの数値だけでも符号化の道具の良し悪しは判断できる。口達者なNHKもそうだったが、同じ レートでボケボケの画像は何か大きく間違っていることだけを示す。その状態ではどの道具(または道具の構成)も信用できない。2つ道具があるとき 1つの道具は最良に調整して初めて次の道具の調整に移ることができる。それでもまだ最良ではない。互いに影響するパラメータでありえるからである。 後に私が思うのは、単に我々両者の意思疎通に問題があっただけでなく、彼らの符号化の知識とプログラムの技術に対する欲求が低かったのである。 技術者は、本当のことが何かを求めなくてはいけない。そして、他人の行為の善意を信じられず、自分の努力に対する信頼がないのである。 計算機言語の道具を使って、正しく努力すれば、凡人もどこまで仕事上の山を登れるかを信じるべきである。
知識は、それ自身に意味がある。何かの世界の客観的な事項の認識方法を与える知識は、それを見出した人だけでなく、誰にとっても有益であり、 価値あるものになる。個人的な知識は、職業訓練の意味の場合もあるだろう。しかし、知識は直接的な価値ではない。それ自身に意味があるが、 価値ではない。価値は、関係性によって決まる。必要な人は代価を支払えばよいが、支払うだけで知識は手に入らない。納得できるように体系を 構築するにはその段階を支える前の段階がいる。知識が正しく納まるためには、個人は知識を自分なりに整頓する必要がある。 未整頓の知識は本来の働き(その次の技術の開発や法則の発見)を発揮しない。知識は再構築のあるべき場所に納まって初めて、その次が可能である。
それは社会に出るのに役に立つためでもない。勿論、その役割と意味もあるが、それが知識獲得の目的ではない。さらにいえば、知識はそれ自身のため でないとその意味がない。頭を整理するためにだけでもよい。行動を正しくするためでもよい。誤った行為を避けるためでもよい。自分のためであり、 ひいては共同社会のためでもある。そのようなものしか頭に叩きこむ意味はない。私のように中学高校のどこかで円周率を40桁覚えても、 最初の8桁以上が役に立つものではない。100桁覚えている友人がいたが、円周率の暗記は、それ自身に全く意味はないが、 知識を求める意欲は明確に表していた。その友人とは高校から最近まで親交し、永く議論してきた私の数人の半理解者のひとりである。 何かを行うには、それを支える無用な知識が沢山沢山必要である。その無駄な知識に円周率があっただけである。私は彼からマシンのコマンドの 意味や扱い方をどれだけ教えてもらったことだろう。そのエンジニアとしての無償の労力に私は感謝しなければならないのである。
インタプリタからコンパイラへの速度の向上は大きい。インタプリタは、実行時にソースの各行の解釈を伴う。昔の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言語が使われる。
実際の処理は、できるだけ無駄を避け、簡潔にしないと間違いやすく仮に結果が正しくても、処理時間が掛かり非実用的になる。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 では * で一発なのに。
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)の実行時の時間的ルーチン使用比率をみて、 比率の大きい主要なルーチンを知ることが重要である。主要ルーチンは改良し速度を上げると、全体の速度が向上する。小比率のルーチンは改良しても、 改良の効果は小さい。
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を使用した。
命令は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の流れ、それらを単に自動的変化や、 過去の無駄な変化と捉えることはできない。設計は理由があって変化し、それには誤りと試行錯誤もあったと思う。
例えば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系を指した。)
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)))
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
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)++);
使用目的や、命令セットによって命令の使用頻度が違わないとすれば、アーキテクチャを決定すれば、命令セットは命令を記述する符号として、命令の実際の 使用頻度に合わせて、長さの違う命令を再設計すればよい。命令セットの圧縮は、命令のVLC化で可能だが人間の認識しやすさを犠牲にする。通常のアセンブラ の必要なビット数は、数bit程度だろう。しかし、問題はそこにはなく、私がこれを実際に作ったように、COSMAC がアセンブラなしに機械語を16進キーと16進デ ィスプレイがあれば、直接打ち込めることである。
光でも音でもよいが、一定強度の信号を発する点信号源が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の重力波検出ではこの方法が使われた。
現代的な良質を求める符号化は、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)