次々と行われる立法措置
新しい「今月のニュース」欄登場にご注目を
Linuxユーザーとプログラマを囲む法律環境が急速に変わる騒ぎが始まって3ヶ月になる。これらの変化−新法と新提案−に対応する適切な記事を提供したい。米国とアイルランドしか知らないので、他国の情報をご教示頂きたい tell us 。一国の立法が他国に影響するので、これは大変重要だ。
どんな法律についても、推進者は誰か?何が狙いか?誰が損をするか(罰金、懲役など)を調べなければならない。何故かと云えば、推進者は、立法者が思ってもいなかった方法で法律を利用する秘密計画を持っており、これが通過して公衆が認めた後、もっと大きい法律を作る計画を持っているからだ。法律の持つ推進者さえも思っていなかった効果とは何かのすべてに、今答えることは出来ないが、問題は提起したい。
特に最後の点。この欄は、予期しない効果を持つ法律に注目する。最近そのような例が余りに多いからだ。
8月号と9月号で、デジタルミレニアム著作権法 Digital Millenium Copyright Act (DMCA)からの予期しない影響について報告した。企業はこれを、見せかけのデザインに対する批判の抑制、そんな見せかけのデザインから顧客が身を守るのを妨げること、さらには自国では合法のプログラムの(自国内での)作成につき懲役を課するため、利用している。ドミトリは25年の刑を受ける可能性がある。企業利益への傾斜は、それが架空のものであっても、強盗よりひどいものだと思う。
Alan Cox (Linux kernel 開発者で前Usenix コーディネータ) のような外国プログラマは、ドミトリのような罠に引っ掛からないよう、米国を無視している。米国人プログラマは、正常であってセキュリティ開発とセキュリティ証明に必要な手続きが非合法である國を探している。多くあるわけではないが、DMCAの主推進者(五つのメディア発行とソフトウエア発行コングロマリット)は、施行された米国法をカナダ、ヨーロッパ、FTAAで同様の法律をつくらせるのに利用している。
9月に、変化は「高速」から「超高速」に切り替わり次いで「オーバードライブ」になった。出版大手5社は、DMCAに「抜け穴」(DMCAが許す最後の公正使用)を塞ぐ目的の甘い提案を持って帰って来た。Security Systems Standards and Certification Act (SSSCA)(セキュリティシステム基準と認証に関する法律)[draft text] [another draft] は、「第104条の下で採用されたセキュリティシステム基準に合致する認証済みセキュリティ技術を含み且つ使用していない会話的デジタル機器による製造、輸入、公衆への提供、作成その他の取引」を非合法であるとする。
「セキュリティシステム基準」とは、デジタル著作権を強化するソフトウエアを意味する。新規IDE機器全部のファイアウォールに迂回不能管理を置のを諦めたCRPMを覚えておられるだろうか?多数の顧客が購入を拒絶したので、放棄された。それなのにSSSCAはこれと同じことをしようとしている。
Linux Weekly Newsは「デジタル機器の定義は文字通り極めて広い。ビットを移動させ記憶する能力のあるハードウエア又はソフトウエアが全部含まれる。特にLinuxを走らせるコンピュータは、無料プログラムを走らせるので確実に含まれる」と言う。Linux自体に苦情が申し立てられると無料ソフトウエア推進者は心配している。LinuxはKernelに権利管理コードを入れることは出来るが、オープンソースなので、どのプログラマもこれを無効にすることが出来る。会社は権利アルゴリズムを秘密にしようとするだろうから、バイナリ形式でだけ入手出来るようにする。Linux版を作ることが出来るだろうか?出来たら、Linuxはそれを所有権のあるバイナリのみのライブラリを標準Kernelにリンクして、これを強制するようLinuxのライセンスを変更しなければならないのだろうか?Kernelは、「米国以外にお住まいなら、最大の融通性、信頼性、速度、セキュリテイを得るため"N"を選んで下さい。米国にお住まいなら、"N"を選ぶのは重罪なので、先に進む前に弁護士と相談して下さい」とのヘルプテキストの付いた新モジュール"digirights.o" を持つことになるのだろうか?
別の問題は、技術基準を決めるのが「会話的デジタル機器製造者の代表と著作権所有者の代表」であることだ。公衆の代表はどうなのだ?著作権の構成権は著者の利益(収入)と公衆の利益(有限期間後にすべてを公有とすることによる科学と有用芸術の進歩、及びある期間の公正平使用権)との間のバランスである。だが、この法律は大出版社だけの著者の利益に偏っている。
法案には独占禁止法の適用例外もある。
公正使用の妥協点が残っている。TV時間共有だ。「放送、無料有料放送、無料衛星放送」でああればTVの録画は許される。有料ケーブルチャンネルの録画は非合法だ。ラジオ、ウエブ放送又は将来技術には条項がないので、これらの公正使用はない。
SSSCAは未だ法律ではなく、9月始めには議会にも提出されておらず、議論されているだけだ。これらの推移に注意しよう。
9月11日に、悲劇が世界中を見舞った。前から言われていたことなので、Linux関連の事項に拘る。突然に、秘密の裏口と秘密の輸出制限がまた流行し始めた。議会は早急に反テロ法律を作るのに追われた。これら法律には良くて必要なものもある。しかし、ロビイスト達と政府代理人達は、問題は永く望まれていた無関係の問題を滑り込ませる余地を見付け、それを探すことを恥じなくなった。ロビイストの夢は、議会がテロリストの邪魔をする立法の通過に熱心で、善悪の判断に十分な時間を掛けないことにある。
勿論、暗号化裏口に反対する通常の議論はある。
テロ攻撃の正確に1週間後、コードレッドの子ニムダ・ウイルスが現れた。法務省はその反テロ法案にハッカー、ウイルス作者及びウエブサイト破壊者を釈放無しの終身刑に処する「テロリスト」と見なすとの文言を入れた。「有価物入手目的、又は重大な損害を起こすためコンピュータを破壊すること」は公務員の殺傷又は化学兵器使用と同じ刑を受ける。法律は、始動の時期に遡って適用されるので、遙か昔に犯した犯罪でも、この下で裁かれる。Kevin Mitnickに取って幸いなことに、既に刑を受けていた。だがウエブサイトの雑貨屋に押し入り無料で銃の包みを注文する者に注意する方が良い。「暗号不正使用者に助言又は援助、又はコンピュータ侵入者の保護隠匿」をおこなう者も侵入者自体と同じ刑罰を受ける。「DNAサンプルは説得によりハッカーから、及び昔は連邦の保護又は監督下にあった者から集められる。サンプルは現在殺人者及び誘拐者の目録を作る連邦データベースに入る」。
反テロリズム法とDMCAが交わったらどうなるか?答。Drmitryは終身刑になる。いや、そうは提案されていない。だが、「ハッキング」の定義に注意し、正確に何を含み、将来拡張されるかどうかを監視しよう。予期しない結果の法則を思い出そう。
オラクルの社長ラリー・エリソンは国家IDカードを要求してりう。これは、IDカードシステムがデータベースを必要とし、何故オラクル・データベースなのかに思い至るまで単に、関係市民を楽しませるだけだと思われていた。エリソンはソフトウエアを政府に無料で提供すると言ったが、オラクルの広告になるのだろうか。(マイクロソフトがソフトウエアを学校に無料で提供したように。今では、ソフトウエアを学校に売っている・・・)ID自体については、この報告書this reportで使う国と使わない国を眺め、元の目的と現在の用途との相違に注目している。(ここでも、予期しない結果の法則)
明るい面もある。議会は乗合反テロリズム法の分割を考えている。緊急を要するものは直ちに通過させるが、論議のあるものは、悪影響を確かめるまで懸案とする。
9月末、上院にSSSCAが上程された。今でも下院には上程されないほど、法律になるまで長い道程だった。今年中に通過するには多くの批判があると多くが予想しているが、その一部はいつか取り上げられるだろう。
linux Journalのドン・マルチは、「コンピュータ検閲法案、SSSCA、で取引をした。議会を買収している」と、ウォルトディズニイ社会長ミッチェル・アイスナーを批難した。また「ホーリング上院議員は思ったより安い」との表題でフリッツ・ホーリング上院議員も批難し、議案上程のためアイスナーがホーリングに$18,500-を支払ったと言っている。勿論、支払いは継続政治献金の形で、直接小切手支払いではない。
「公正使用騒ぎの間コピイ制限上院議員は居眠り」の記録では、ホーリングの選挙運動への寄付者トップ20の中に、大手5社、AOLタイムワーナー、マードック所有通信社、バイアコムのCBS、放送協会、ウォルトディズニイ社の大手5社が含まれている。「ホーリング上院議員選挙運動ディズニイ」とYahooに打ち込むと、この記事を含めた色々な記事が出て来る。(他の議員名を入れても色々分かる)
製造者の立場からSSSCAを眺めた記事がある。 Bill Could Force Copy Control On IT Firms. はSSSCAが「産業の注意を製品開発からロビイ活動に向けさせて規制を求めるので、技術開発を遅らせる」と言う。ある弁護士は言う「どのシステムが役立つかを言って誰か他人の所有権を保護するのは、馬鹿げたことだ」
別の記事が言うには、SSSCAは「業界が圧力を認めるDMCAの中の消費者の襟を巻く嫌らしい穴をすべて塞ぐ」。「好都合なことに、陰謀により、構成使用の語句は法案の何処にもない。業界のロビイストはDMCAの中のややこしい語句を好まないので、消してしまった」。その記事はまた、SSSCAは新しい親切なハードウエアが、古いハードウエアと交換出来ることを求めない、同じ内容をもう一度買わせることを意味する、と指摘する。
電子フロンティア基金 Electronic Frontier Foundation (EFF) の役割は、活動家の話題に出現した。多くの人はEFFをロビイ活動組織と思っているが、その使命は、不当に訴追された人を守ることにある。議会人達に全力をあげたロビイ活動をすること、はEFFの構造を変え、ロビイストに口を挟む人の間で染まり、組織を別の税範疇におき、防衛する故人から引き出す。
こうして、無料ソフトウエアと「ドミトリ釈放」グループは、自分でロビイ活動をしようかと考えるようになった。勿論、最初の旗揚げはワシントンから出来るだけ遠ざかることだが、我々がしなかったら、誰がするのか?勿論、それは面白くないことだ。議会人の投票のため選挙運動をするようなものだ。無料ソフトウエア社会にPAC(政治活動委員会)を賛助する用意があるだろうか?誰かは、それを我々の権利に対する急速な攻撃に穴をあける唯一の方法と考えている。同意を得るのに長い時間が掛かることは確かだ。
IT記事で指摘するような別の明るい面もある。メディア会社は、彼らが何を望んでいるか見守っている筈だ。予期しない効果の法則は多分それを適用して終わるだろう。特に、十分多くの人が大手5社のコンテントなしでやって行けると思ったときだ。
又は、芸術家がその音楽を無料で放棄したときだ。ショーが面白くなり、楽団は一つのコンサートでもっと沢山の金を手にするだろう。ショーでCDを買う人がいれば、両方が儲かる。買う人は安く買えるし、楽団は小売りの25セントでなく数ドルを手にする。
そして会社の利益は少なくなる。
結論として、DMCAは通過し(数人を除いて)誰も気にしなかった。遅過ぎる今になって、その影響を見ている。「安全」と思った国々でもDMCAに似た法律が推進されている。このとき出版社は帽子から別の法律と一緒に兎を取り出し、DMCAのように、それが通過したとき思ったより遙かに強力になるのを誰も気付かない。
これは、秘密の裏口とテロ関連ハッキングその他と組み合わされて、今とは大きく異なる50年後の社会を予想させる。そこでは、何物も見る度に金をとられ、公正使用は忘れられて、利用出来るあらゆるコンピュータがこれを強化し、Linuxはとっくの昔に非合法になっているので誰も覚えていない。AOLタイムワーナー、ウォルトディズニイ、マイクロソフトなど大手会社だけが親しまれている。法律は、法律採用のとき支配的であった会社に有利な競争条件を与える。彼らは、存在外の誰でも訴えるのに法律を利用するからだ(熱心に推進する理由が他にあるか?)。今日コンピュータ分野の多くの人々は、失望して他の分野に転じるだろう、対面を好んでe-メイルや電話を遠ざけるだろう。こじつけだって?多分そうだろう、だが政治家は経営者は信用出来ないので、最悪を考えるのだ。
勿論、強力な暗号化のため出版社を必要とするか否かと弱い暗号化のためFBIを必要とするか否かは衝突コース上にあると思う。FBIの必要性(悪者を捕らえるための)は既に見た。e-取引の必要性(健康な経済促進のための)は反対だ。だからこそクリントン政権は暗号輸出法を弱め、FBIが押し戻している。
興味ある別の問題
"Anticircumvention Rules: Threat to Science" (科学雑誌)は、コンピュータセキュリティと暗号研究だけでなく、すべての科学者に対する脅迫だと論じる。「明らかにコンピュータ科学者は、プログラミング技術を持つ他の多くの科学者と同様、時に技術コンピュータプログラムを逆転する必要を見出す。時には、プログラムの働きを見るため、不具合を直すため、何かに適合させるためなどで、アルゴリズム過程や他の技術手段をを迂回しなければならない。アルゴリズム過程や他の技術手段をを迂回する行為は、技術過程逆転を助けるツールと同様、DMCAに抵触する。DMCAはまた、プログラム逆転の例外を有しているが、それは狭すぎる。逆エンジニアリングの唯一の目的がプログラム対プログラムの相互作用性を達成するときでそのため逆エンジニアリングが必要なとき、適用されるだけである。優先権のある逆エンジニアリング過程で偶然知った情報は、プログラム対プログラムの相互作用性を可能にするだけの目的を除いて誰にも漏らしてはならない。「条文はまた、薬品会社について『新薬が安全であることを証明するためデータを作成するが、データに一定の試験だけが出来るよう技術的に防衛、それらすべてが安全性に関する権利主張を支援する』と推測する。安全性に関する権利主張を疑い追加試験によりデータを処理仕様とする科学者は、データ使用を制限する管理システムを迂回すると、DMCAに抵触する。」
サイバースペースのコードとその他の法律を書いたローレンス・レッシグに、LWNの出ニス・テニイが会見した。彼はDMCA、ドミトリ・スクリャノフの事件、ヘイルストーム、国際裁判、などに付いての見通しを述べた。
DeCSS case.DeCSS事件の背景。
DMCAの余り知られていない情状を幾つか述べた Slashdot post 。
PGP暗号を作ったフィル・ツィンメルマンは、ワシントンポストにどんなに間違って報道されたかを説明した。同紙は、彼が9月11日のテロリスト達が攻撃計画に当たってPGP暗号を使ったかも知れないことにつき「罪の意識にさいなまれている」と報じた。ツインメルマンは、強力な暗号に公衆が裏口なしでアクセスするのは、良い考えだと述べ、PGPは世界中の人権のため良いツールだと述べた。彼はまたPGPに何の裏口も許さないと強調した。
|
|
|
●Python10コンファレンス
第10回Python10コンファレンス Tenth International Python Conference (Python 10) が2002年2月4−7日に、バージニア州アレキサンドリアのヒルトン・アレキサンドリア・マークセンターで開催される。論文と解説はwww.python10.org/p10-callpapers.html, とwww.python10.org/p10-calltutorials.html, にそれぞれ応募されたい。論文提出期限は、2001年10月8日、解説提案提出期限は2001年10月1日。
●LLNL がASCIプロジェクトにLinuxクラスタスーパーコンピュータで加勢
SGIの子会社SGIフェデラルは、Linux NetworX とチームを組んで、原子核安全管理局力(National Nuclear Security Administration'の加速戦略計算運動( Accelerated Strategic Computing Initiative (ASCI))向けの、ペンチアム4を全部で472個使用する3並行処理リソース(PCR)構築の入札に勝利を目指す。ASCIは2005年に100兆回/秒の計算処理を達成する計画で、核兵器の経年変化と運用をシミュレートして科学者が米国核兵器貯蔵の安全性と信頼性を保つのを助ける。857ギガFLOPの理論的最高性能を用いて、最大でペンチアム4プロセッサ252個を持つPCR P4Aと言う名の三つのシステムが、今まで作られた最大のLinuxクラスタとなる。
●石油ペンギン
ハリバートン社が全株を所有するランドマーク・グラフィックス社Landmark Graphics Corp.は本日、Linuxプラットホーム用の統合Linux探査生産アプリケーションを2001年第4四半期に発足させる計画を発表した。これは石油及びガス業界の大手技術社によるLinuxオープンソースOS支援の、これまで最大の事業となる。コンパック、デル、EMC、IBM、インテル、ネットワークアプライアンスがランドマークが、ワークステーション、サーバー、記憶装置を含む最適Linuxソリューションの提案に協力している。
アダム・ディ・カルロは、ブートフロッピイのバージョン3.0.14が、パワーPCとi386用のテスト用に入手出来るavailable と発表した announced 。試してご意見を debian-boot@lists.debian.org. Original story.に送られたい。
Debian セキュリティはユーザーに極めて重要なので、正しく運営しなければならない。Recently最近、シミュレーション改良を助けるため、ジョェイ・ヘスが、セキュリティ担当になった。レポートはDebian Weekly News.で得られる。
●SuSE
8月末から、インテルの32ビットアーキテクチャ(x86)、インテルの64ビットアーキテクチャ(イタニウム・プロセッサ・ファミリー)及びIBMのS/390用に、SuSE Linux Enterprise Server 7 が利用出来る。IBMのiシリーズ、pシリーズ、zシリーズは秋の終わりなる。販売価格には、SuSE Linux Enterprise Server 7 を常に更新、試験する保守サービスが含まれる。SuSeは定期的にe−メイルでユーザーに連絡し、それぞれのパッチ、フィックス、アプデートをFTPサーバーで入手出来るようにする。詳しくは次ぎを参照http://www.suse.de/en/sles/
SuSE Linux はまた、SuSE Linux Database Server がSuSE Linux Enterprise Server のOSプラットホームをIBMのDB2データベースと結合して、専門ユーザーの完全なソリューションにした、と発表した。SuSE Linux は、ソリューションパケージの更新版を提供する。
MEN Micro は、新しいCompactPCI単一ボードコンピュータ(SBC)を発表した。SBCには三つのバージョンがあり、各バージョンが300 MHz PowerPC XPC824を含む。D3は、新SBCの指定では、1スロット6U Compact PCIボードである。CompactPCIシステムでは、マスターシステムボードとして働くが、埋込アプリケーションでは、バス接続なしにスタンドアローン処理装置としても働く。コンピュータとして、D3はSODIMMスロットの256KのDRAM、フラッシュメモリ2MB、ATA互換CompactFlashサイトに、多数のI/O装置をつけて供給される。D3は、VxWorks でも Linux でも働く。データシートなどの詳細については、http://www.men.de/products/press/.へ。
● Rockspaceが"Best Dedicated Host"の称号を獲得
2,500名以上のLinuxサーバーのホストであるRackspace,は、Web Hosting Magazine 編集者の判定で、顧客サービスを評価され"Best Dedicated Host"の称号を得た。
● SAIR Linux及びGNU認証
最近、Linux+認証活動の促進を助けるため、CompTIA's Linux+n 認定試験の準備コースウエアを二倍にした基礎コースウエアSAIR Linux and GNU Certification を発表した。CompTIAがSAIR Linux とGNUに接触して、CompTIAのLinux認証試験の開発を要請した。コースの題名は、SAIR Linux and GNU Fundamentals/Linux+.
● Linuxベースのイーサメット互換Set Top Box
Media Technology は、新製品 VT900 Set Top Box. を立ち上げた。VT9000は、イーサネット10/100データ流を、TVセットと互換性のあるアナログRFデータ流又はデジタルs−ビデオデータ流に変えるのを可能にする。250チャンネル以上の各種TVフォーマットがVT9000で試験され成功した。Linuxを実行すると、VT9000は全ブラウザを含み、すべてのプラグインをサポートする。Century Embedded Software Inc.及びEnreach Technology Incと提携して、さらにアプリケーション・ソフトウエアを開発する。VT9000は、ナショナル半導体コードプロセッサをシグマ設計EM8400 MPEGデコーダ及び Macphyterイーサネットアダプタを使って設計された。オプションでDVDプレーヤ、CDRW、フロッピイ、標準IDEハードウエアが利用出来る。
● ASAチームが二重ギガビットイーサネットポートを用いるNPWR SBCアップグレードを発表
Team ASA's NPWR は、ネットワーク接続記憶(NAS)、RAID、個人サーバー市場における製造者及びOEMのため設計された単一ボードコンピュータ(SBC)である。これは今や、二重ギガビットポートを付けて利用出来る。NPWRはXScaleプロセッサで強化された。XScaleは、733MHzに達するクロックレートを持つRISC CPUである。NPWRの標準構成には、160メガバイト/秒のSCSIポート、8メガバイトのフラッシュROM、128メガバイトとSDRAM、ギガビット・イーサネットを含む。
● DocPro DocBook ツールセット
Command Prompt, Inc. は「専門家用DocBookツールセット」DocProを発表した。DocProは、技術ライターがDocBook SGML とXMLを効果的処理出来るよう設計された組合せツールである。DocBook自体が、強力なマークアップ言語であるけれども、Command Prompt社との協同で、これらツールを生産環境に統合するにあたっての問題点をなくした。CocProは、ベーシック及びデラックスのバージョンで供給され、LinuxのRedHat6.2以降のバージョンで働く。
● Tarantella EnterpriseのLinux用3 Starter
Tarantella, Inc. は、Tarantella Enterprise 3 Starter for Linux ソフトウエアが利用出来ると発表した。この製品は、 Windows、Web、Java、AS/400、 Linux 、UNIX のアプリケーションを、どこにあるクライアント装置に確実に公表することを可能にする。これにより、リモートシステム管理、即ち過程から会社のアプリケーション及びサービスにアクセスすることが出来るようになる。
● 埋込Open Motif
● WAP Opera
Opera Software は、そのブラウザの将来バージョンが新規改良型無線アプリケーションプロトコル(Wireless Application Protocol )(WAP) 2.0 基準をサポートすると発表した。 announced 。
Opera Software はまた、改修 MyOpera コミユティ開き、Operaコンポーザの二版を発表して、ユーザーを招いている。この新バージョンでは、ユーザーが自分のウエブブラウザをウインドウズ用やLinux用に改造することが出来る。
● Alabanza
Alabanza は、Automated Web Hosting Software Suiteのバージョン4.1を立ち上げた。これは元々Linux OS用に構築されて今でも働いている。ソフトウエアセットの最新版には、OnNet Web Hostingから取得した新ウエブサイト構築ツールが含まれる。バージョン4.1は、ウエブサイト管理問題を自動化し、ユーザーのサイト更新力を強め、完全なセキュリティを備え、すべての小企業に電子商業の機会を提供する。AlabanzaのAutomated Web Hosting Software Suiteのバージョン4.1は、多数のウエブ設計者、開発者、システム統括者、インターネットサービス・プロバイダ、通信業者のための、最も信頼性があって安全なソリューションである。これを用いてエンドユーザーは、短い時間と少ない費用でしかも安全に、ウエブサイトの管理業務を自動化し、内容を管理し、更新することが出来る。
● BrainTAGS
http://www.braintags.de/ はNetRelayを発表した。このソフトウエアツールは、ウエブのクライアント、サーバー、データベース間の処理を自動化する。これには、自動記録作成、削除、表示、更新及びテンプレートエンジンが含まれる。NetRelayの高度なアーキテクチャにより、ダイナミックウエブアプリケーションの開発が、透明で構造的になる。NetRelayは論理と説明の区別を明確にし、自動XMLドキュメントを作成し、データ転送を容易にする。NetRelayはデータベースに無関係である。NetRelayは、JDKをサポートするサーバープラットホームで働き、Linuxでもテストされた。
1.緒言
すべてを与えてGUIはLinux社会で有名な商標となった。これは益々多くのプログラム作者がGUIベースのコンフィギュレーションをおこなっていることを意味する。
しかし、GUIを使うとき忘れていることがある。ドキュメンテーションだ。マウスに慣れた人は「何故プログラムを読まなければいけない?マウスで十分だ」と考える。
ドキュメンテーションを読むのを薦めるのは良いことだ。ドキュメンテーションが良い程、プログラムは簡単になる。例えばApacheウエブサーバーには、沢山のドキュメンテーションが付いている。その結果、英語の少し分かる人は誰でも、マウスを使わないでApacheを使いこなすことが出来る。
この記事では、プログラマーにそのプロジェクトをドキュメンテーションにすると同時に、そうしながら考えをまとめることを薦める。
2.ドキュメンテーションを書く理由
理由は沢山ある。ドキュメンテーションが多い程使い易くなる。ドキュメンテーションが多いことは、アドオンモジュールが多いことを意味する。ユーザーが楽になることを意味する。プログラム特性を生かそうとする人は、先ずドキュメンテーションを読む。良いドキュメンテーションを作るとユーザーがプログラムの特性を生かして使える。
3.プロジェクト/プログラムのどの側面を書くべきか?
一般的に、プロジェクト又はプログラムの次の側面を書く。
フォーマットとしては、好みのテキストエディタを使う。それで全部書けるようにする。
読みやすいスタイルで書く。詩人になってはいけない。
好適なのは平易な英語だ。コンピュータ使用者のほとんどが理解出来る。母国語のドキュメンテーションは何時でも追加出来る。ドイツ語やロシヤ語は誰でもが話すわけでない。少なくとも基本的部分には英語を含む。ドキュメンテーションの最も簡単な部分が理解出来ない人は、そこを読まないだろうし、プログラムを使わないことが多い。
残りは、貴方次第。ドキュメンテーション作成は、開発の最も面倒な部分であることをいつも心に留めること。
5.ドキュメンテーションのフォーマット
5.1ドキュメンテーションに適したファイル・フォーマット
標準のものを用いる。専用のものは誰も好まない。MS Word, StarOffice などは避ける。
最も簡単なフォーマットはテキスト形式だ。何処でも誰でも読める。
印刷可能にしたいときは、LATEX が良い。LATEX は、システム無関係の出力フォーマットでHTMLに移出できる。
現在の最適の選択はHTMLだ。ハイパーテキストで、すべてのプラットホーム用にリーダーがあり、オンラインで読めるようにホームペイジで配布出来る。
DocBook 又は LinuxDoc/SGMLのようなSGML- 又はXML-ベースのフォーマットも使える。これらは、他の任意のフォーマットに変換するのに最も融通性がある。
5.2ソースコードのドキュメンテーション
ソースコードは、コメントと適切な変数名をを沢山入れて簡単に書くことが出来る。外部ファイルでのAPI記述が重要である。拡張出来るライブラリなどを書くとき特にそうである。
6.HTMLを用いるドキュメンテーション・フォーマット
常に最も共通のHTMLサブセットを用いる。ドキュメンテーションにフレーム又はJavaスクリプトを使わないこと。セクションには <H1></H1>から<H6></H6>、パラグラフには<P></P>を、簡単に使う。詳細はHTML使用法を見ること。
7.LyXの利用
これは、プログラム・ドキュメンテーションを書くヒントだ。分かるように書くのがLATEXに入力し、ASCIIやHTMLに移出する特徴だ。LATEXをあらわすある種のGUIを考えること。LATEXは、WYSIWIGテキスト・プロセッサでないことに留意すること。
LyXは、ほとんどのLinuxディストリビューションに付いて来る。試すこと。
LyXの移出特性は、この目的に良く合っている。ドキュメンテーションの .dvi ファイルを作ると、ほとんど全部のLinuxボックスに印刷可能でプリント出来る。だから、印刷可能マニュアルのドキュメント作成でも容易だ。
8.PDFについて
PDFはPostscriptの拡張版だ。ドキュメンテーション上の最大の欠点は、正しく見えるグラフィック・ディスプレーかプリンタを必要とすることだ。
PDFはまた、Linux、HTML及び"just work"マニュアルの上で信頼性が劣る。ソフトウエアを正しく搭載するとLATEXファイルは働くが、PDF編集各種ツールやビューワと互換性がないので、ドキュメントを開けない人が出て来て、テキストが白紙になる。
PDFファイルはまた、大きくなり過ぎる。図や表があるときは特にそうなる。PDFファイルの表示速度は、HTML, DVI 又は普通のテキストに比べて遅い。ドキュメンテーションには推薦しない。
9.まとめ
正しいツールを使うとドキュメンテーションは楽しい仕事だ。ドキュメンテーションは誰でも読めるように作るのが重要。
良いドキュメントを作るのは、努力にあたいする。
この記事は、読者にコード最適化の一端と出来上がったオブジェクトコードの効率増加をを理解して頂くため、GNU Cコンパイラを用いるコード最適化技術を述べる。
1.緒言
周知のように、コンパイラはソースプログラムを高水準言語で読んで機械語に翻訳するプログラムである。これは多数のステップを含む複雑な処理である。コンパイラが最適化コンパイラであると、一つのステップが、処理時間を短縮し使うメモリを少なくするよう、機械語を最適化する。最適化がプログラムの意味を変えてはいけない。最適化は、どのようにおこなうのだろうか?コンパイルするプログラムの意味を変えてはいけないので、コンパイラは、プログラムを慎重に検査して、出来る最適化を見出す。この過程は複雑で時間の掛かる処理で、その詳細はこの記事の範囲を超える。
ここでは、コンパイルの方法と最適化処理を理解できるよう、GNU Cコンパイラが用いるコード最適化の型を幾つか述べる。GNU Cコンパイラは、沢山の最適化技術を用いて効率的なコードを作る優れた最適化Cコンパイラである。全部を述べることは出来ないので、面白くて分かり易いものを選んだ。GNU Cコンパイラが使う最適化技術すべてのりすとは、http://www.redhat.com/products/support/gnupro/gnupro_gcc.html. にある。特定の最適化技術が何かを述べる代わりに、GNU Cコンパイラが作ったアセンブリ言語コードを使って説明する。この方法はまた、読者が、コンパイラのおこなうコード最適化と最新最適化技術を、研究することが出来るようにする。
2.Cプログラム用アセンブリ言語コード
GNU C コンパイラ(以後GCCと言う)Cプログラム・ソースファイルを読んで、バイナリ形式の機械語コードを含むオブジェクトプログラムに翻訳する。しかし、オブジェクトプログラムの代わりにアセンブリ言語ソースコードもまた作ることが出来るので、アセンブリ言語プログラムを読んで理解することが出来る。コンパイラが用いる最適化を理解するためアセンブリ言語ファイルを作るので、簡単なCプログラムのアセンブリ言語コードがどうなっているかを理解するのが役立つだろう。しかし、作ったコードの各アセンブリ言語ステートメントを理解する必要はないので、コード最適化理解に重要でないステートメントは説明しない。アセンブリ言語コードを作るには、以下に示すように、ファイル test1.c を作って次のコマンドを与える:
$ gcc -c -S test1.c
これは test1.c ファイル test1.c を作る。Cプログラムのため作ったコードのアセンブリ言語一覧が入る。ファイル test1.c をアセンブリ言語一覧と共に下記に示す:
1 : /* test1.c */ 2 : /* この第一ファイルは単に、コンパイラの作るアセンブリプログラムが
3 : どのように見えるかを示し、MASM/TASMと違う習慣にしたがうGNU
4 : アセンブラの変わった点を示す
5 : */
6 : #include <stdio.h>
7 :
8 : int main()
9 : {
10 : printf("Hello, World\n");
11 : return 0;
12 : }
13 :
14 : /* end test1.c */
15 : /* ----------------------------------------------------------------- */
16 : /* 作成アセンブリ言語ファイル */
17 : .file "test1.c" /* 幾つかのアセンブラディレクティブ */
18 : .version "01.01" /* は無視する */
19 : gcc2_compiled.:
20 : .section .rodata /* このセグメントは読取専用データを持つ */
21 : .LC0:
22 : .string "Hello, World\n"
23 : .text
24 : .align 4
25 : .globl main
26 : .type main,@function
27 : main: /* メインファンクションの始まり*/
28 : pushl $.LC0 /* printf() 用パラメータをスタックにプッシュ */
29 : call printf /* ファンクション呼び出し*/
30 : addl $4,%esp /* スタックをクリヤ */
31 :
32 : xorl %eax,%eax /* EAX = 0 にする,ファンクションがレジスタを使う*/
33 : jmp .L1 /* EAX が値を返す*/
34 : .p2align 4,,7 /* これはアライメント・ディレクティブ */
35 : .L1:
36 : ret /*メインからリターン, 終わり */
37 : .Lfe1:
38 : /* 他のアセンブリ・ディレクティブは無視 */
39 : .size main,.Lfe1-main
40 : .ident "GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"
41 : /* 作成したアセンブリ言語ファイルの終わり */
42 : /* ------------------------------------------------------------- */
80386以上のマイクロプロセッサのアーキテクチャをご存じで、アセンブリ言語プログラムの経験があれば、出来上がったアセンブリ言語の一部は見慣れたものであろう。このアセンブリ言語はAT&Tアセンブラ・シンタクスにしたがうので、ほとんどの人が見慣れた Intel/Microsoft アセンブラ/Turbo アセンブラのシンタクスと異なる。アセンブリ言語コードを概観しよう。作成コードの理解に重要でないから、アセンブラ・ディレクティブは無視する。20行目で、中に"Hello, World\n" の文字列を持たせて読取専用データセグメントを定義している。文字列にはラベル.L20を指定してあ。27行目からmain()ファンクションの定義が始まる。C言語では、パラメータはそれらをスタックにプッシュしてファンクションに渡される。この場合は、単一パラメータ、文字列 "Hello, World\n" を持たせて printf() ファンクションを呼び出す。ステートメント pushl $.LC0 が文字列 "Hello, World\n"のアドレスをスタックにプッシュする。ステートメント pushl $.LC0 の "l"は、32ビット変数を扱うので "long" をあらわす。これから見るアセンブリ言語プログラムではすべて、"l" の付いた記号は32ビット変数を扱うことを示す。次のステートメントが printf() ファンクションを呼び出す。 .LC0 の前の "$" は、文字列のアドレスを意味する。次のステートメントが printf() ファンクションを呼び出す。printf() ファンクションが実行を終えた後、スタックをクリーンアップしなければならないので、4 to ESP付け加える必要がある(4なのは、printf()呼び出しの前に4バイトをスタックにプッシュしたからである)。そうするには、通常 ADD ESP, 4と書く筈だ。インテルのやり方は、 <instruction> dest, srcである。AT&Tのやり方は、<instruction> src, destとなるので、30行目の命令が addl $4, %espであるのがお分かりだろう。4野様な直接演算数は前に $ を付け、レジスタ名は前に % を付ける。このやり方は、BSDアセンブラとの互換性を保つため踏襲している。次のステートメントは後に EAX = 0 となるようEAXを自分自身とXORする。これは、ファンクションからの戻り値がEAXレジスタに記憶されるからである。その後に、ジャンプ命令とアライメント命令がある。これらは説明の必要がなく、この時点で main() ファンクションからリターンすることを知れば十分であろう。アセンブリ言語の模様が分かったので、最適化に進む。
3.定数フォールディング(Constant folding)
定数フォールディングは、最も理解し易いコード最適化である。Cプログラムにステートメント x = 45 * 88 を書いたとしよう。最適化しないコンパイラは45に88を掛けて値xを記憶するコードを作る。最適化コンパイラは45と88が定数であろから、その積もまた定数であることを見出す。こうして、45 * 88 = 3960 を見出し、単に3960xに写すコードを作る。これが定数フォールディングで、計算を、プログラムが走る時でなくコンパイルの時、1回だけおこなうことを意味する。これを示すため、下記のようにファイル test2.c を作る。このプログラム用のアセンブリコードを前に示したように作る。規定値で、GCCはプログラムを最適化しないので、アセンブリコードはCプログラムの直訳になっているのが分かる(18行から62行まで)。
1 : /* test2.c */
2 : /* 定数伝達の実演 */
3 : #include <stdio.h>
4 :
5 : int main()
6 : {
7 : int x, y, z;
8 : x = 10;
9 : y = x + 45;
10 : z = y + 4;
11 : printf("The value of z = %d", z);
12 : return 0;
13 : }
14 : /* test2.c の終わり */
15 :
16 : /* ---------------------------------------------------------------- */
17 : /* 全く最適化していないアセンブリ言語ファイル */
18 : .file "test2.c"
19 : .version "01.01"
20 : gcc2_compiled.:
21 : .section .rodata
22 : .LC0:
23 : .string "The value of z = %d"
24 : .text
25 : .align 4
26 : .globl main
27 : .type main,@function
28 : main:
29 : pushl %ebp /* EBP レジスタをスタックにセーブ */
30 : movl %esp,%ebp /* EBP = ESP */
31 : subl $12,%esp /* スタックフレーム. 3 変数 x 4 バイトを作る */
32 :
33 : /* x = 10; */
34 : movl $10,-4(%ebp) /* x = 10. x はスタックの一番上にある */
35 :
36 : /* y = x + 45; */
37 : movl -4(%ebp),%edx /* EDX = x */
38 : addl $45,%edx /* EDX = EDX + 45 */
39 : movl %edx,-8(%ebp) /* y = EDX. y はスタックの上から二番目にある */
40 :
41 : /* z = y + 4 */
42 : movl -8(%ebp),%edx /* EDX = y */
43 : addl $4,%edx /* EDX = EDX + 4 */
44 : movl %edx,-12(%ebp) /* z = EDX. z はスタックの上から三番目にある*/
45 :
46 : /* printf("The value of z = ", z); */
47 : movl -12(%ebp),%eax /* EAX = z */
48 : pushl %eax /* printf の第一パラメータとして EAX(=z) をプッシュ */
49 : pushl $.LC0 /* printf の第二パラメータ */
50 : call printf
51 : addl $8,%esp /* スタックをクリヤー */
52 :
53 : /* return 0; */
54 : xorl %eax,%eax /* 0 を返すため */
55 : jmp .L1
56 : .p2align 4,,7
57 : .L1:
58 : leave
59 : ret
60 : .Lfe1:
61 : .size main,.Lfe1-main
62 : .ident "GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"
63 : /* end of assembly language code */
64 : /* ---------------------------------------------------------------- */
65 :
66 : /* ---------------------------------------------------------------- */
67 : /* 最適化後に作成されたアセンブリ言語コード */
68 :
69 : .file "test2.c"
70 : .version "01.01"
71 : gcc2_compiled.:
72 : .section .rodata
73 : .LC0:
74 : .string "The value of z = %d"
75 : .text
76 : .align 4
77 : .globl main
78 : .type main,@function
79 : main:
80 : pushl %ebp /* EBP レジスタをスタックにセーブ */
81 : movl %esp,%ebp /* EBP = ESP */
82 :
83 : /* 定数伝達により、z は常に 59 */
84 : /* printf(" z = %d の値"、 z); */
85 : pushl $59 /* printf 第一パラメータ */
86 : pushl $.LC0 /* printf 第二パラメータ */
87 : call printf
88 : /* クリーンアップ不要、とに角エクジット */
89 : /* return 0; */
90 : xorl %eax,%eax
91 : leave
92 : ret
93 : .Lfe1:
94 : .size main,.Lfe1-main
95 : .ident "GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"
96 : /* アセンブリ言語コード終わりe */
97 : /* ----------------------------------------------------------------- */
アセンブリ言語コードには理解しなければならない幾つかのことがある。まず、main() ファンクションは三つのローカル変数を含み、これにスタック上のスペースを割り当てること。これら変数にアクセスするため、間接アドレスモードを使い、そのためEBPを使うこと。だから、第一ステートメントがEBPをスタックにセーブする。次いで、ローカル変数へのアクセスにEBPを使うことが出来るよう、スタックポインタESPをEBPにコピイする(ソースESPが先)。最後に、三つの4バイト変数のためのスペースを、スタック上に作らなければならないので、ESPから12を引く。つまり、スタックを12バイト大きくする。xが最低部のスタックエレメントでEBPから-4だけオフセットしている。下図を参照。
EBP-----> ---------------
| x | 4 バイト
---------------
| y | 4 バイト
---------------
| z | 4 バイト
ESP-----> ---------------
| |
| |
^ | |
| ...
上にゆくにつれ | | |
アドレスが増える | 0 ---------------
同様に、yのEBPからのオフセットは -8 で、zは -12 である。ここで x = 10;の指定を考える。34行目でこれを実行するステートメントは、movl $10, -4(%ebp) である。間接モードアドレス指定は <offset>(<base register>) のように書くので、上のステートメントは定数 10 をアドレス(EBP - 4)、つまり x のスタック上のアドレス、にコピイする。同様に、yへのアドレスには -8(%ebp) を、z へのアドレスには -12(%ebp) を用いる。37、38、及び39行は、 x + 45 を計算して、結果をyに割り当てる。そうするには、xの値を先ずEDXレジスタにコピイし、それに45を加えて、EDXの中にあるその結果をyにコピイする。z = y + 4のためのコードも同様である。47行目では、 printf() のためのパラメータをスタックにプッシュしている。ます最後のパラメータ(z)をプッシュしてから、文字列のアドレスをプッシュする。51行目では、ESPに8を加えてスタックをクリーンアップする。4バイトパラメータ二つをプッシュしたので、ESPに8を加えたと推定する。最初の例では、パラメータ一つだけをプッシュしたので、ESPには4を加えるだけだ。コードの残りの部分は分かり易い。
上のプログラムを見ると、 x + 45を計算するとき、x = 10の指定があるのでxの値は常に10になる。したがって、x + 45は常に55と計算される。だから、このステートメントはyを常に55と指定する。同様に、y + 4 を計算すると、結果は常に59になる。最適化をおこなうと、コンパイラがこの状態を検出する。最適化が出来るようにするには、-O2オプションを用いる。次のコマンドを入力する。
gcc -c -S -O2 test2.c
最適化を働かせて作ったアセンブリ言語コードを69行目から95行目までに示す。85行目でコンパイラが、文字列アドレスの続くスタックに、定数59を直接プッシュしてから、s printf()を呼び出す。こうして、定数フォールディングにより、コンパイラはプログラムの中の式を1回だけ計算して最終値を作ったコードに詰め込むことが出来る。もう一つ面白いのは、定数フォールディングの後、変数が x, y, z が不要になり、これらのためのスペースをスタック上に割り当てる必要がないのでプログラムの必要メモリが減ることである。一つの最適化が別のものも導き出すこととなる。上の場合では、定数フォールディングが作動時間の短縮(計算はコンパイル時だけ)と必要メモリの減少をもたらしている。
4.共通計算式の除去
同一プログラム内の異なる場所で同じ式を計算し、しかも式の中の演算数が二つの計算の間で変わらないことが何度もある。例えば、プログラムが a * b を初めと終わりに計算するとする。 a と b の値が、二つの a * b の計算の間で変わらないなら、 a * b を二度計算する代わりに、初めの計算結果を一時変数として記憶して終わりに使う。これはプログラム内での余分な計算をなくする。これを共通副計算式の除去(common subexpression elimination)という。これを示すため、次のプログラムを考える。
1 : /* test3.c */
2 : /* 共通副計算式の除去と定数伝達 */
3 : #include <stdio.h>
4 :
5 : int main()
6 : {
7 : int a, b;
8 : int x, y, z;
9 : scanf("%d %d", &a, &b);
10 : x = a * b;
11 :
12 : if(b >= 4)
13 : {
14 : y = a * b;
15 : z = 0;
16 : }
17 : else
18 : {
19 : z = a * b * 4;
20 : y = 0;
21 : }
22 :
23 : printf("x = %d, y = %d, z = %d\n", x, y, z);
24 : return 0;
25 : }
26 : /* test3.c の終わり */
27 :
28 : /* ----------------------------------------------------------------- */
29 : /* 最適化しないで作成したアセンブリ言語コード */
30 : .file "test3.c"
31 : .version "01.01"
32 : gcc2_compiled.:
33 : .section .rodata
34 : .LC0:
35 : .string "%d %d"
36 : .LC1:
37 : .string "x = %d, y = %d, z = %d\n"
38 : .text
39 : .align 4
40 : .globl main
41 : .type main,@function
42 : main:
43 : pushl %ebp /* EBP をセーブ */
44 : movl %esp,%ebp /* EBP = ESP */
45 : subl $20,%esp /* 五つの変数のためスペースを作る */
46 :
47 : /* scanf("%d %d". &a, &b); */
48 : leal -8(%ebp),%eax
49 : pushl %eax /* b のアドレスをスタックにプッシュ */
50 : leal -4(%ebp),%eax
51 : pushl %eax /* a のアドレスをスタックにプッシュ */
52 : pushl $.LC0 /* 文字列のアドレスをスタックにプッシュ */
53 : call scanf
54 : addl $12,%esp /* scanf後のスタッククリーンアップ */
55 :
56 : /* x = a * b; */
57 : movl -4(%ebp),%eax /* EAX = a */
58 : imull -8(%ebp),%eax /* EAX = EAX * b = a * b */
59 : movl %eax,-12(%ebp) /* x = EAX = a * b */
60 :
61 : /* if( b >= 4)... */
62 : cmpl $3,-8(%ebp) /* b を 3 と比較 */
63 : jle .L2 /* 合えば、ラベルl .L2 のelse part へ */
64 :
65 : /* y = a * b; */
66 : movl -4(%ebp),%eax /* EAX = a */
67 : imull -8(%ebp),%eax /* EAX = EAX * b = a * b */
68 : movl %eax,-16(%ebp) /* y = EAX = a * b */
69 : /* z = 0; */
70 : movl $0,-20(%ebp)
71 : jmp .L3 /* else over 部分 に jump */
72 :
73 : .p2align 4,,7
74 : .L2:
75 : /* ここから else 部分 が始まる */
76 :
77 : /* z = a * b * 4; */
78 : movl -4(%ebp),%eax /* EAX = a */
79 : imull -8(%ebp),%eax /* EAX = EAX * b = a * b */
80 : leal 0(,%eax,4),%edx /* EDX = EAX*4 + 0 */
81 : movl %edx,-20(%ebp) /* z = EDX */
82 : /* y = 0; */
83 : movl $0,-16(%ebp)
84 : .L3:
85 : /* if..else はここで終わる */
86 :
87 : /* printf("x = %d, y = %d, z = %d\n", x, y, x); */
88 : movl -20(%ebp),%eax
89 : pushl %eax /* z のアドレスをスタックにプッシュ */
90 : movl -16(%ebp),%eax
91 : pushl %eax /* y のアドレスをスタックにプッシュ */
92 : movl -12(%ebp),%eax
93 : pushl %eax /* x のアドレスをスタックにプッシュ */
94 : pushl $.LC1 /* 文字列のアドレス */
95 : call printf
96 : addl $16,%esp /* printf の後、スタックをクリーンアップ */
97 :
98 : /* return 0 */
99 : xorl %eax,%eax
100 : jmp .L1
101 : .p2align 4,,7
102 : .L1:
103 : leave
104 : ret
105 : .Lfe1:
106 : .size main,.Lfe1-main
107 : .ident "GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"
108 : /* 最適化しないアセンブリ言語コードの終わり */
109 : /* --------------------------------------------------------------- */
110 :
111 : /* --------------------------------------------------------------- */
112 : /* 作成した最適がアセンブリ言語コード */
113 : .file "test3.c"
114 : .version "01.01"
115 : gcc2_compiled.:
116 : .section .rodata
117 : .LC0:
118 : .string "%d %d"
119 : .LC1:
120 : .string "x = %d, y = %d, z = %d\n"
121 : .text
122 : .align 4
123 : .globl main
124 : .type main,@function
125 : main:
126 : pushl %ebp /* EBP をセーブ */
127 : movl %esp,%ebp /* EBP = ESP */
128 : subl $8,%esp /* スタックに二つの変数だけのスペース */
129 :
130 : /* scanf("%d %d", &a, &b); */
131 : leal -4(%ebp),%eax
132 : pushl %eax /* b のアドレスをスタックにプッシュ */
133 : leal -8(%ebp),%eax
134 : pushl %eax /* a のアドレスをスタックにプッシュ */
135 : pushl $.LC0 /* 文字列のアドレス */
136 : call scanf
137 :
138 : /* x = a * b; */
139 : movl -4(%ebp),%eax /* EAX = b */
140 : movl %eax,%edx /* EDX = EAX = b */
141 : imull -8(%ebp),%edx /* EDX = EDX * a = b * a = a * b */
142 :
143 : addl $12,%esp /* 遅延スタック・クリーンアップ */
144 : /* if( b >= 4).... */
145 : cmpl $3,%eax /* EAX = b を 3 と比較 */
146 : jle .L17 /* else 部分は label .L17 から */
147 :
148 : /* y を ECX に, z を EAX に, x を EDX に記憶 */
149 : /* y = a * b; */
150 : movl %edx,%ecx
151 : /* z = 0; */
152 : xorl %eax,%eax
153 : jmp .L18 /* over the else 部分にジャンプ */
154 : .p2align 4,,7
155 : .L17:
156 : /* z = a * b * 4; */
157 : leal 0(,%edx,4),%eax /* LEA EAX, [EDX*4]+0 */
158 : /* y = 0; */
159 : xorl %ecx,%ecx
160 : .L18:
161 : pushl %eax /* z の値をプッシュ */
162 : pushl %ecx /* y の値をプッシュ */
163 : pushl %edx /* x の値をプッシュ */
164 : pushl $.LC1 /* 文字列のアドレスをプッシュ */
165 : call printf
166 : /* printf 後のスタッククリーンアップは不要 */
167 :
168 : /* return 0; */
169 : xorl %eax,%eax
170 : leave
171 : ret
172 : .Lfe1:
173 : .size main,.Lfe1-main
174 : .ident "GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"
175 : /* 最適化アセンブリ言語コードの終わり */
176 : /* -------------------------------------------------------------- */
このプログラムは共通計算式の例を有する。10行目で初めて、式 a * b を計算し、続いて14行目と19行目で計算する。aもbも初めの計算から値が代わっていないから、後の二つの a * b 計算は冗長である。これら二つは除去出来る共通計算式である。30行から108行までは、Cコード直訳の最適化しないアセンブリ言語コードを示すので、分かり易い。ここで、113行から176行までの最適化したアセンブリ言語コードを考察する。最初に気付くのは、変数aとbだけがスタックに記憶されることだ。だから、最適化しないときの20バイトに代えて8バイトだけのスタックで済む。変数aとbだけが特別なのは、変数aとbのアドレスがプログラムで使われ(scanf()のため)るが、レジスタ内にある変数値はメモリアドレスを持ち得ないからである。だからプログラムは、これらをレジスタの中に置くことが出来ない。他の変数のためにコンパイラが選ぶレジスタは、x に EDX、 y に ECX 、z に EAX である。130行から141行までで a * b を計算し、その値を(xを入れる)EDXに記憶する。また、bの値はレジスターEAXの中で利用出来る。もう一つ気付くのは、143行で、scanf() 呼び出し後の遅延クリーンアップである。多分その長所があるのだろうが、私は知らない。
次ぎにifステートメントが始まる。 if 部分では、式 a * b を再度計算するので、コンパイラはEDXに記憶された値を使うと期待する。コンパイラはその通りにする。ステートメント y = a * b のため、150行に作ったコードがレジスタEDXにある a * bの値をレジスタ(yを入れる)ECXにコピイする。else 部分にもまた、z = a * b * 4ステートメントの中に共通副計算式があるので、コンパイラはEDXにある a * bの値を使って4を掛け、結果を(zを入れる)EAXに記憶すると期待する。これをおこなうには沢山の方法がある。一つは一連の movl, imull 命令を次のように使うことだ。
movl %edx, %eax /* EAX = EDX = a * b */
imull $4, %eax /* EAX = EAX * 4 */
2番目の方法は、上の imull の代わりにシフト (sall) を使うことだ。しかしGCCは、難しい仕掛けをコード最適化に使う。次の命令を使う。
leal 0(,%edx,4), %eax
この命令は、80386プロセッサのスケーリングとインデキシングの能力を使う。上の命令は、EDXをインデクスレジスタ、4をスケール、0をオフセットとして使い、有効アドレスを計算してEAXレジスタに記憶する。こうして、レジスタEAXが、値EDX (=a*b)*4 + 0 つまり a * b * 4 を得る。80386上では、leal 命令は常に、2クロックサイクル以内で実行されるので、単純シフトは約7クロックサイクルで終わる。コンパイラがプロセッサの特性を知っていて、コード作成にそれを使うことが分かる。
こうして共通副計算式が多くの計算を省略し、それら冗長計算に必要なスペースをなくすることがわかる。ここで、最適化のためプログラムを解析するに当たって、コンパイラは変数が何時どのように変わるか、使用した計算式及び変数に割り当てられるレジスタを追跡し続けなければならないことが分かる。一般的に、コンパイラがおこなうこのような解析をas data flow analysis(データ流通りの解析)と言う。
5.デッドコードの削除
デッドコードとは入力その他の条件のため実行されることないプログラム中のコードである。普通の例は if ステートメントである。if の中の条件が真になることはないのをコンパイラが見出すと、そのifステートメント本体は実行されることがない。そのような場合コンパイラは、そのデッドコードを完全に削除して、メモリ空間を節約する。デッドコード削除を以下のプログラムで示す。
1 : /* test4.c */
2 : /* デッドコード削除の例 */
3 : #include <stdio.h>
4 :
5 : int main()
6 : {
7 : int x;
8 :
9 : scanf("%d", &x);
10 :
11 : if(x < 0 && x > 0)
12 : {
13 : x = 99;
14 : printf("Hello. Inside the if!!!");
15 : }
16 :
17 : return 0;
18 : }
19 : /* end of test4.c */
20 :
21 : /* --------------------------------------------------------------- */
22 : /* 最適化アセンブリ言語コード */
23 : .file "test4.c"
24 : .version "01.01"
25 : gcc2_compiled.:
26 : .section .rodata
27 : .LC0:
28 : .string "%d"
29 : .LC1:
30 : .string "Hello. Inside the if!!!"
31 : .text
32 : .align 4
33 : .globl main
34 : .type main,@function
35 : main:
36 : pushl %ebp /* EBP をスタックにセーブ */
37 : movl %esp,%ebp /* EBP = ESP */
38 : subl $4,%esp /* x 用のスペースをスタック上に作る */
39 :
40 : /* scanf("%d", &x); */
41 : leal -4(%ebp),%eax
42 : pushl %eax /* アドレスをスタックにプッシュ */
43 : pushl $.LC0 /* 文字列をスタックにプッシュ */
44 : call scanf
45 :
46 : /* if 本体と点検条件の全文が、デッドコード */
47 : /* return 0; */
48 : xorl %eax,%eax /* スタッククリーンアップ無し、兎に角 exit */
49 : leave
50 : ret
51 : .Lfe1:
52 : .size main,.Lfe1-main
53 : .ident "GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"
54 : /* 最適化アセンブリ言語コードの終わり */
55 : /* ---------------------------------------------------------------- */
最適化しないアセンブリ言語コードは役に立たないし、分かり易いので省略した。プログラム中、11行目の条件 x < 0 && x > 0 は真になることが出来ない。コンパイラはこれを見出して、ifステートメントはデッドコードであると判断し、そのためのコードを作らない。一つ面白いことに気付く。文字列"Hello. Inside the if!!!" は、もう必要ないので、読取専用データ部分から削除することが出来る。しかし、ここまでするとコンパイラが複雑になるので、おこなっていない。
6.誘導変数を用いる強度減少
コード最適化の一つの型は、「高価な」演算を廉価なものに代える強度減少である。例えば、x2の計算は、指数ルーチンを呼び出すよりxにxを掛ける方が遙かに効率が良い。こうした最適化の出来る一つの場所は、ループである。ループでは何度も、一つの変数がループ変数に同期して変化する。このような変数を、induction variable誘導変数と言う。誘導変数は、コンパイラに強度減少の機会を与える。次のプログラムから分かる。
1 : /* test5.c */
2 : /* 誘導変数削除の例 */
3 : int main()
4 : {
5 : int i, j;
6 :
7 : for(i = 0; i < 10; i++)
8 : {
9 : j = i * 7;
10 : printf("i = %d, j = %d", i, j);
11 : }
12 : return 0;
13 : }
14 : /* end test5.c */
15 :
16 : /* --------------------------------------------------------------- */
17 : /* 最適化アセンブリ言語コード */
18 : .file "test5.c"
19 : .version "01.01"
20 : gcc2_compiled.:
21 : .section .rodata
22 : .LC0:
23 : .string "i = %d, j = %d"
24 : .text
25 : .align 4
26 : .globl main
27 : .type main,@function
28 : main:
29 : pushl %ebp /* EBP をスタックにセーブ */
30 : movl %esp,%ebp /* ESP = EBP */
31 :
32 : pushl %esi /* ESI が 'j' を保持 */
33 : pushl %ebx /* EBX が 'i' を保持 */
34 : xorl %ebx,%ebx /* 双方を0に初期化 */
35 : xorl %esi,%esi
36 : .p2align 4,,7
37 : .L5:
38 : /* printf("i = %d, j = %d", i, j); */
39 : pushl %esi /* j の値をプッシュ */
40 : pushl %ebx /* i の値をプッシュ */
41 : pushl $.LC0 /* 文字列のアドレスをプッシュ */
42 : call printf
43 : addl $12,%esp /* スタッククリーンアップ */
44 :
45 : /* j = i * 7と言う代わりに, j = j + 7 と言う方が効率的*/
46 : addl $7,%esi
47 : incl %ebx /* i++ */
48 : cmpl $9,%ebx /* i <= 9 ならループを反復 */
49 : jle .L5
50 :
51 : /* return 0; */
52 : xorl %eax,%eax
53 : leal -8(%ebp),%esp
54 : popl %ebx
55 : popl %esi
56 : leave
57 : ret
58 : .Lfe1:
59 : .size main,.Lfe1-main
60 : .ident "GCC: (GNU) egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"
61 :
62 : /* 最適化アセンブリ言語コードの終わり */
63 : /* ----------------------------------------------------------------- */
ここで i はループ変数で、 j は常に固定ステップで変化するので誘導変数である。作成した最適化アセンブリ言語コードで、コンパイラはiとjの双方を、それぞれEBXとESIのレジスタを使って記憶することに決めた。34行目がforループの初期化部分で指示された通りEBX (=i)を0に初期化する。コンパイラは第一パスの間に、jにもまた値0が割当られるのを知って、35行目でESIを0に初期化する。ループは35行目から始まって49行目まで続く。jには既に値が割り当てられているので、コンパイラがループ内で先ずおこなうのは、 printf() ファンクションの呼び出しである。ここでコンパイラは、ステートメントの順序を調節して、強度減少が使えるようにしている。プログラムを解析した後、コンパイラはループの中で、値iが常に1だけ増加し、jは誘導変数なのでその値が常に7だけ増加することを理解する。そこで、iに7を掛ける(これは高価)代わりに、次の繰り返しに行く前にjの古い値に7を加える。こうして高価な乗算を(時間の点で)廉価な加算に代えた。
7.まとめ
この記事では、GNU Cコンパイラが作成コード最適化のため使用するコード最適化技術の極めて基本的なことを眺めた。これら最適化技術と、それらを適用するため必要な各週情報から、読者はコンパイラがプログラムにおこなう高度で複雑な解析の型を理解されたと思う。これには、変数、その記憶場所(メモリかレジスタか)、式の計算、定数式、デッドコードなど各種の事項を追跡し続ける。プログラムを最適化出来るようにしたコンパイルに長い時間を要するのは、このためである。コード最適化とコード作成に関して、説明しなかった詳細及び筆者の知らないことが沢山ある。コード最適化は、現在研究中の分野なので、興味ある読者は追加情報に付いて、[1]を参照されたい。
8.謝辞
コンパイラとコード最適化に興味を持たせて頂いたUday Khedker 博士に感謝する。また筆者の論文出版にご協力頂いたLinux Gazette, Linux Documentation Project, PC Quest and the Pune Linux User's Group (http://www.pluggies.org/) に感謝する。
9.参考文献
9月6日、稼働中のハードディスクにフォークを落としてしまった。アンサーギャングに質問したところ、Nick Moffittがコンピュータでやってしまった馬鹿なことを色々教えてくれた。これはその返事だ。
フォークを落とした場合
過熱防止のため私はコンピュータのカバーを開けたままにしておく。フォークが机からコンピュータケースの中に落ちてハードディスクと電源をまたいだ。幸い電源の中に入らなかったが、フォークを取ったら、neditファイルのセーブが出来なくなっていた(読取専用になった)。ディスクのランプは連続的に点いたままだ。 'mount' を走らせたら、入出力エラーが出た。ドライブが飛んだと考えて、neditウインドウでPytonに加えた変更全部を書き出した。 "su -c mount /Backups" を試して、パックアップパーティションを搭載しようとしたが、何もしてくれなかった。Xセッションを出ようとしたら、コンピュータがフリーズした。リセットを押したが、BIOSに渡してくれない。コンピュータの電源を切って少し待ち、また入れた。今度はうまくブートした。
コンピュータは数日働いて、夜中に故障し始めた。 ROCK Linux をインストールしようとしたが、完了出来なかった。HD二つを繋ぐかどうかは、問題でなかった。ハードディスル以外にコンピュータの何かが悪いと思って、部品を取って別のコンピュータを組み立てた。AMD Duron 800 、一番安い HD (10 GB)、PCI イーサネットカードとサウンドカードに、ATX ケースだ。.次号では、このROCKの搭載について述べる。
失敗談はないが、高校でコンピュータ・ハードウエアにやり慣れたことがある。
弟の学校に沢山のIBMモデルBsがあったので持ち帰った。部屋にコンセントが少ないので、そのほとんどは役に立たなかった。
友達が、66MHzか何かで働くカードから外したクリスタルを沢山持っていた。
そこで、廃物のベニヤ板から海賊船を作って、モデルBをその上に置いた。電源を入れ、クリスタルを入れた。 4.77MHz チップがゆっくり熔けて臭いを出したが、船は燃えなかった。そこで湯船に入れて、海賊船の葬式を出した。
長年コンピュータをやっているが、馬鹿なことは思い出さないが、・・・Linuxだけに少しある。
私のラップトップについてやった馬鹿なことは、何かのため必要になる前に、すべてのアップグレードを1日でやったことだ。アップグレードは危険だ。
I/Oカード Chris Gianakopoulos
一番の失敗は、I/Oカードを私のS-100 (CP/M)システムから抜いてまた差したことだ。ハードウエア修理中にCPMがリブートする三秒を待ちきれなかったためだ。大胆な40幕の後、最後にI/Oカード真っ直ぐ入れたため沢山の火花が飛んだ。
取り出して二週間後に、直そうと思った。コンピュータは勝手なときに故障して、フロントパネルのランプは点いたままだ。四ヶ月後、周波数カウンタを手に入れて、システムクロックが、あってはならないところに重なって(superposeして)いるのを見付けた。信号定義とコネクタピンを見て、銅の屑がシステムクロックを別のピンにショートしているのを見付けた。それ以来電子工学を目指して、油断しなくなった。
それだけでなく、電源を切らないでI/Oを出し入れしてしまったのだ。
Johnから:
未だに486/100を持っていてSlackwareを走らせている。暫く使わなかったが、SCSI HDのお陰でXを何とか働かせる。
私のシステムがディスクバッファフラッシュをする度に、SCSIをラップトップで提案してくれたらと思う。IDE HDを使っているのを思い出すため、画面の周りでカーソルを動かそうとするとショートして暫く止まる。SCSI HD を使うIMOなら、マシンに巨大なRAMがあるのと同じになって、こんなことは起こらない筈だ。
Chris から:
リニアシステムの本を読み過ぎた。幾つかの本にsuperposedの言葉があり、他の本はsuperimposed の言葉を使っている。兎に角、数学関数を別の数学関数に代数的に加算する意味だ。例えば、5ボルト直流信号の上に240ミリボルト正弦波信号を乗せることが出来る。すると、5ボルトのオフセットを持つ240ミリボルト正弦波が得られる。
言ったことは分かって貰えると思う。"superpose"の様な単語をどこでひねり出したかと思われたのだろう。この言葉は普通リニアシステムへの重畳に使う。重畳については、説明させないで欲しい。
とにかく、私の見たのは、あるべきでない場所に1Mhz信号を見付けたのだ。
Iron から:
重ね合わせた(superimposed)ような何かだと想像する。貧弱な電子工学の知識では、システムクロックがどこかで自分に重なるのは想像できないし、何故問題なのかもわからない。
Chris から:
今になって分かった。コンピュータの電源を入れたままプリント板を抜いた。至る所焼けたコンピュータを修理した後、コンピュータを立ち上げることが出来なかった。コンピュータのフロントパネルライトは(異常に)点いたままになった。最後に問題を解決したとき、銅屑がシステムクロック信号を、カードエッジコネクタの隣のピンにショートしているのを見付けた。銅屑は、何度も抜き差ししたとき、I/Oカードのエッジコネクタが剥げ落ちたものだった。
理由は、君と同じで、待ちたくないからだ。挑戦するOSはブートに7分掛かる。Linuxは2分だ。
一番の失敗だって?電源ファンが故障して、386CPUが過熱しシステムが壊れたことだ。だから日に3回か4回、大きい袋に氷を詰めてCPUの真上に置く。今のところうまく行っている。
滴の垂れるラップトップ John Karns
この春のこと、ラップトップを他のものと一緒にバックパックに入れて友達の家に運んだ。バックパックの中には水筒も入れた。水筒の蓋を固くねじ止めしなかったので・・・何が起こったか分かるでしょう。行き先に着いてバックパックを車から出したとき、座席が濡れていた。パックパックの中身を調べてラップトップを出したとき、横向きに縦に持つと滴が落ちた。
マシンを開けて一部分解した。ファンの風を当ててテーブルの上に置いた。こんな時重要なのは、マシンの電源を入れないことだ。家に帰ってから、一晩そのままにした。幸い、HDに故障はなく、マシンは助かった。画面に少し白斑点が出たが、数週間で無くなった。濡れたのが古いので、新しく買った方でなくて良かった。
失敗談があったら gazette@ssc.com に知らせて下さい。掲載します。
GNU/Linux上の数値ワークベンチに関するこのシリーズのパートIとパートIIでは、マトリクス取扱やforループのような無味乾燥な話題を扱った。このパートでは、GNU/Octave、Scilab、Telaのグラフィック機能を扱うので画面に色が付く。前のように三つのアプリケーションを示す訳には行かない。グラフィックのバックエンドは大き過ぎて僅かな説明で一様に扱うことは出来ないからだ。
この記事は、すべてのプログラムに当てはまる緒言から始まる。読者の便のため、すべてのアプリケーションは同じ三つの実生活問題を解く。実行法は違っても、これで比較が容易になる。最後の部分でOctave、Scilab、Telaは与えられた課題に取り組まなければならない。
緒言
{ sin(x)/x for x <> 0
y := f(x) = {
{ 1 for x == 0
そのままではグラフに出来なくて、ディスクリートな値 (x(i), y(i)) に変換しなければならないことを意味する。この転換をサンプリングと言う。 f(x)をサンプルするには、f(x) の興味がある場所でxを選び、与えられたxについてf を計算する。これまでのパートを理解した読者は、これがベクトル演算を含むことを直ちに理解されるであろう。
### GNU/Octave code
function y = f(x)
if x == 0.0
y = 1.0;
else
y = sin(x) ./ x;
endif
endfunction
x = linspace(0.0, 4*pi, 50);
y = f(x);
linspace(start, end, n) は、均等な間隔のn要素のベクトルを start から end まで返す。ベクトルx とy は適切な描画関数を渡すことが出来る。
データが既にベクトル型になっているときは、直ちに表示することが出来る
x = a \ b # Octave, Scilab
または
x = linsolve(a, b) // Tela
呼び出しをわざと複雑にしようとしても(そのままで役立つのだが)最後には
x = linsolve(a, b, "CompletePivoting", "DoIterativeRefinement")
にぶつかる。これは、二つのパラメータ−解のピボット戦略と反復精製−を用いて、ユーザーは完全に linsolve() を制御する。他のすべての「デシジョン」はワークベンチ自体が合理的に取り上げる。例えば、マトリクスが特別の形を持つときどんなアルゴリズムを使うかなどだ。
これを
x = [2.25, 2.27, 2.42, ...]
y = [0.363, 0.360, 0.337, ...]
の2b-グラフと対照されたい。どんなオプションが考えられるだろうか?
この他にも多数のオプションが考えられる。問題は、プロットが多元方程式を解くなどのように簡単に使えるとは期待出来ないことだ。アプリケーションが悪いのではなく、課題が本来ユーザー側で複雑にされているのだ。したがって、Octave, Scilab, Telaで出版品質の線を描くのに20行以上を必要とするのは、驚くに当たらない。
問題
比較を面白くするため、実世界から余り離れない問題を作った。三つのアプリケーションそれぞれが三つの違う型のプロットを扱わなければならない。
単一頁上に3組のデータ(l1.ascii, l2.ascii, l3.ascii)を表示する。3組はデータ点の数が異なる。
データは二列に記憶されており、第一列が x-値、第二列が y-値である。
0.2808 3.419E-07
0.3711 3.459E-07
0.4882 3.488E-07
...
グラフには題名、軸名、凡例を示さなければならない。xとyの範囲はユーザーが与える。
f(u, v) = 100*(v - u^2)^2 + (1 - u)^2
プロットに題名と軸名を注釈する。
g(u, v) = exp(u) * (4*u^2 + 2*v^2 + 4*u*v + 2*v + 1)
を等高線付きでプロットする。これは与えられたzについて、パラメータ範囲 -4.5 <= u <= -0.5 と -1 <= v <= 3 のf(u, v) = z である。
等高線はユーザー規定の「加重」ファンクションを用いて定義される。
( z - z_0 ) 3
z_iso = (z_1 - z_0) * ( --------- ) + z_0
( z_1 - z_0 )
ここで、z_0 とz_1 は、プロット範囲内での g(u, v) の最小値と最大値である。
今回も、プロットに題名と軸名を注釈する。
グラフィックはすべて、二つの装置用とみなす
Octave
Gnuplot をご存じだろうか? ご存じなら、GNU/Octaveのプロット機能使用法の学習は簡単だ。しなければならないすべては、"g" を用いてGnuplot コマンドを進めOctave環境に入ることだ。ご存じなければ、実例でご指導するが、Gnuplotのマニュアルがあると役立つ。
Gnuplotのマニュアルをダウンロードするのが面倒なら、公開版のうち一つを利用出来る
Octave:ディスクリートデータの二次元プロット
コードをブロックに分けて、話を進めやすくする。各ブロックには、互いに従属するコマンドを集める。ブロックには角括弧で番号を付けて、説明文中で参照出来るようにする。
### [1] Read dataset_i into N_i-times-2 matrix
set1 = load("l1.ascii");
set2 = load("l2.ascii");
set3 = load("l3.ascii");
### [2] Gnuplot をリセット
graw("reset;");
clearplot;
### [3] 飾りとプロット領域を定義
gset title "Comparison of sets L1, L2, and L3";
gset xlabel "Temperature / K";
gset ylabel "Voltage / V";
gset key top left;
gset xrange [0 : 100];
gset yrange [8e-8 : 2e-6];
### [4] データをプロット
hold on;
gplot set1 title "Set L1" with points;
gplot set2 title "Set L2" with points;
gplot set3 title "Set L3" with points;
hold off;
### [5] PostScript 出力に切り換えてファイルにプロットする
gset terminal push;
gset terminal postscript;
gset output "oct1.eps";
replot;
gset terminal pop;
gset output;
Postscript ターミナルへの出力、ブロック[6]、がグラフィックのプリント可能版(eps)を作った。
ブロック[1] は前の記事で明らかな筈だ。Gnuplot との最初の会話は[2]で起こり、ここで、Gnuplot が既知の状態にリセットされ X11-plot ウインドウがクリアされる。既知の状態は、、Gnuplotのオプションをさらに試験するのに役立つ。我々の場合、リセットは不要だが、しても構わない。
gset コマンドのブロック[3] は、「説明(又はキイ)をプロット領域内の北西隅に置く」との意味の
gset key top left
を除いて自明だ。xrange と yrange は特有の間隔シンタクスを用いてプロット範囲の幅と高さを設定する。これで、実際のデータをプロットウインドウに反映することが出来る[4]。データセットはサイズが異なるので、単一のマトリクスにまとめることは出来ない。したがって、各データセットを各自にプロットしなければならないから、Gnuplotには−hold onを用いて、セットの最後のプロットか完了し hold off が呼び出されるまで、すべての gplotコマンドを集めるよう指示しなければならない。 gplot への呼び出しは実質的にデータマトリクス
gplot set1
を含む。プロット説明の上に示す題名も、gplotへの呼び出しの中で指定する。
ブロック[5] は、Gnuplotの働き方に特有である。各プロットに terminal と output のファイルを用いる(UN*X では、コンソールなど何でもファイルであることを想起されたい)。GnuplotがXの下で働いているとき、ターミナル型はX11が規定値で出力ファイルはGnuplotのX11グラフィックウインドウが規定値となる。terminal と output のファイルは両方とも gset コマンドを用いて独立に変更することが出来る。そこで、同じグラフィックを違うフォーマットで得るには、現在のターミナルからPostscriptに切り換えて元のファイル名を出力に置く。ファンクションreplotはすべてのコマンドを繰り返すので、もう一度書く必要はない。その後、ターミナルと出力の設定を解除する。これはオープションだが、ユーザーがもっと多くのgsetをひねくり回してグラフの見掛けへの影響を試したいとき役に立つ。
Octave:三次元関数のプロット
### [1] 関数定義
function z = f(u, v)
## 切頭Rosenbrock 関数
z = 100.0*(v - u.^2).^2 + (1.0 - u).^2;
zv = z(:);
zv(find(zv > 100.0)) = 100.0;
z = reshape(zv, size(z));
endfunction
### [2] サンプル関数f()
x = linspace(-3, 3, 40);
y = linspace(-2, 4, 40);
[xx, yy] = meshgrid(x, y);
z_splot = splice_mat(xx, yy, f(xx, yy));
### [3] Gnuplotのリセット
graw("reset;");
clearplot;
### [4] 飾りと観測方向を決める
gset data style line;
gset title "Rosenbrock Function";
gset xlabel "u";
gset ylabel "v";
gset view 30, 160;
gset hidden;
gset nokey
gset parametric;
### [5] プロット
gsplot z_splot;
### [6] PostScript 出力に切り換えてファイルにプロット
gset terminal push;
gset terminal postscript;
gset output "oct2.eps";
replot;
gset terminal pop;
gset output;
gset noparametric;
system("gzip --best --force oct2.eps");
プリンタ準備完了版 (eps.gz) もまた利用出来る。
ここでも第一ブロック[1]、は既知のファンクションを使うだけなので、パートIとIIの読者には理解出来る筈だ。対照的に[2]には二つのファンクションが新しく導入されている。linspace() は、f(u, v) を計算する場所を決める二つのベクトルを素早く作成する。ベクトル x と y からグリッドつまり、マトリクス xx と yy が構築され、ここで、マトリクス要素、(xx(i, j), yy(i, j)), 1 <= i, j <= 40、が関数 z = f(u, v) を計算する(グリッド)点を定義する。そこでそのグリッド点における z-値全部のマトリクスは単に zz = f(xx, yy)となる。しかし、これ終わらない。Octaveは、特別にフォーマットされたマトリクスを3次元プロット関数gsplotに渡すことを要求するからだ。
ユーザー定義関数 splice_mat() は必要な演算、マトリクス z_plot 内の z-値に沿ってグリッドデータ xx と yy を集めること、 を正確におこなう。マトリクス z_plot は、追加の ado 無しで gsplot に渡すことが出来る(Gnuplotがパラメータノードであると(今はパラメータノードであり、与えられた種類の問題に関してはパラメータノードでしか進めない(また別のレベルの括弧を付けようとも思わない(本当だよ!)))。
ブロック[4]、gsets の集積、は見慣れた形だ。新しい設定
help -i gsplot、又はshellから info octave --node='Three-Dimensional Plotting' )
沢山の準備の後では、[5]の実際のプロットコマンド gsplot z_splot は詰まらなく見える。
Postscript ブロック[6] はOctave:ディスクリートデータの二次元プロットの[5] と同様だ。唯一[6]で追加された仕事は eps-ファイルを gzip することだ。一般的に system(``shell-commands'') は、 shell-commands をサブシェルで実行する。明らかにこれは gzip(1).など外部アプリケーションと会話するとき、極めて有用だ。
Octave:等高線関数プロット
### [1] ファンクションを定義
function z = g(u, v)
z = exp(u) .* (4.0*u.^2 + 2.0*v.^2 + 4.0*u.*v + 2.0*v + 1.0);
endfunction
### [2] 等高線距離用に加重ファンクションを定義
function y = pow_weight(x, n)
## 間隔 X をそれ自体の上にマップ、N-乗で加重
d = max(x) - min(x);
y = d*((x - min(x))/d).^n + min(x);
endfunction
### [3] Sファンクション g() からサンプルを取る
x = linspace(-4.5, -0.5, 40);
y = linspace(-1.0, 3.0, 40);
[xx, yy] = meshgrid(x, y);
zz = g(xx, yy);
z_splot = splice_mat(xx, yy, zz);
### [4] 等高線距離を計算
iso_levels = pow_weight(linspace(min(min(zz))*1.01, ...
max(max(zz))*0.99, 12), 3.0);
il_str = sprintf("%f,", iso_levels);
il_str = il_str(1 : length(il_str)-1); # remove last ","
### [5] Gnuplot をリセット
graw("reset;");
clearplot;
### [6] Define 飾りと視線方向を定義
gset data style line;
gset title "Contour Plot of g(u, v)";
gset xlabel "u";
gset ylabel "v";
gset contour base;
gset nosurface;
gset view 0, 0;
eval(sprintf("gset cntrparam levels discrete %s", il_str));
gset parametric;
### [7] プロット
gsplot z_splot;
### [8] PostScript出力に切り換えてフィルにプロットする
gset terminal push;
gset terminal postscript;
gset output "oct3.eps";
replot;
gset terminal pop;
gset output;
gset noparametric;
プリント可能版(eps) も利用出来る。
二次元ディスクリートと三次元関数プロットの例を見た後では、等高線ファンクションスクリプトのブロック[1-3] には余り疑問はない筈だ。しかしブロック[4]には仕掛けを作った。これはブロック[6]の後で述べる。仕事は、ユーザー定義関数を使って等高線を書くことだ。この関数の値したがって等高線の位置は、前もって分かってはいない。
Gnuplot は、等高線を規定する方法を、与えられた数の(線型に間隔をあけた)輪郭の自動計算や、隣接二線間の距離を固定して最大最小輪郭値を決めるなど、幾つか与える。我々の問題は、もっと一般的な解を必要とする。等高線がz-軸に沿って等間隔でないからだ。完全に無秩序な等高線値については、輪郭線を決めるときの第三の方法として、Gnuplotは次の gset コマンドを有する。
gset cntrparam discrete z1, z2, ..., zN
ここで、 z1, z2, ..., zN は、floating point literals として与えられる等高線の z-値である。したがって、
z1 = 0.2
z2 = 0.4
z3 = 0.8
とするとき、
gset cntrparam discrete 0.2, 0.4, 0.8
は完全な呼出となる。
gset cntrparam discrete z1, z2, z3
は、シンタクスエラー以外の何物でもない。
gset cntrparam discrete iso_levels
と同じだ。 gset は浮動小数点記法を必要とすることを思い出されたい!
少し魔術を使わない限り動けない。完全な gset 行、ベクトル iso_levels の値が「組み込まれ」行、を0ctaveに与えられたら勝負は勝ちだ。(Perlプログラマは毎日これをやっている)仕掛けは次の通りだ。
# iso_levels をコンマで分けた文字列に転換。 Octave は、フォーマット
# スペシファイアがあるより多いプリント項目があるとき、このフォーマット
# スペシファイアリストを使う。これは Cの printf には使わないこと。
il_string = sprintf("%f,", iso_levels)
# 文字列中最後の輪郭値を過ぎたコンマを削除
il_string = il_string(1 : length(il_string)-1)
# 内挿トリックを二度目に使う
gset_string = sprintf("gset cntrparam levels discrete %s", il_string);
# 変数gset_string に記憶されたコマンドを実行
eval(gset_string);
抽象的な説明が嫌いな読者のため、これは(長たらしい行を書いて合わせた)学期の修了証だ。
octave:10> il_string = sprintf("%f,", iso_levels)
il_string = 0.583444,0.592029,0.652120,0.815224,1.132847,1.656497, \
2.437679,3.527900,4.978667,6.841486,9.167864,12.009307,
octave:11> il_string = il_string(1 : length(il_string)-1)
il_string = 0.583444,0.592029,0.652120,0.815224,1.132847,1.656497, \
2.437679,3.527900,4.978667,6.841486,9.167864,12.009307
octave:12> gset_string = sprintf("gset cntrparam levels discrete %s", \
il_string)
gset_string = gset cntrparam levels discrete 0.583444,0.592029, \
0.652120,0.815224,1.132847,1.656497,2.437679,3.527900, \
4.978667,6.841486,9.167864,12.009307
スクリプトの中に、一時変数 gset_string は導入されておらず、 sprintf() はその出力を直接 eval() に送る。
ブロック[6]: Gnuplot は輪郭プロットに優れてはいない。事実GNuplotユーザーマニュアルはそれを直接使わないよう教えている。とにかく、先へ進もう。理解するより出る方が容易いからだ。次の三つの gset がGnuplotを輪郭モードに切り換える。
gset contour base # draw contours in the xy-plane
gset nosurface # do not draw the surface's mesh
gset view 0, 0 # view the xy-plane from above
ブロック [7] と [8] はOctave:二次元ディスクリートデータのプロットとOctave:三次元関数プロットの節で既に見たのと似ている
追加のデモプロットはhttp://www.gnuplot.org/gnuplot/gpdocs/all2.htmにある。
Scilab
Scilab は、大きいための複雑さの項で述べた複雑さに別の方法で取り組む。Gnuplotと反対に、Scilabはプロットと設定を厳密に区別しないで、異なるプロットコマンドのplethora (バックルアップしてから apropos plot を試みる)を用いて種類の違うプロット作る。さらに、プロットファンクション自体が多数のアーギュメントを持ち、これがプロットのアーギュメントを変える。アーギュメントの幾つかは、暗号的なので適切なマニュアルを手元に欲しくなる筈だ。
オンラインヘルプを見たいがScilabをインストールしていない読者は、オンラインで手に入れられるScilab manual 。
Scilab:ディスクリートデータの二次元プロット
// [1] Read data set_i into N_i-times-2 matrices
set1 = read("l1.ascii", -1, 2);
set2 = read("l2.ascii", -1, 2);
set3 = read("l3.ascii", -1, 2);
// [2] プロットウインドウの内容をクリア
xbasc();
// [3] データをプロット: 最初のプロットコマンドがプロット領域を定義
plot2d(set1(:, 1), set1(:, 2), -1, "011", ..
rect = [0, 8e-8, 100, 2e-6]);
plot2d(set2(:, 1), set2(:, 2), -2, "000");
plot2d(set3(:, 1), set3(:, 2), -3, "000");
// [4] 飾りを定義
xtitle(["Comparison of sets", "L1, L2, and L3"], ..
"Temperature / K", "Voltage / V");
legends(["Set L1 ", "Set L2 ", "Set L3 "], [-1, -2, -3], 2);
// [5] プロットウインドウ内容をファイルにセーブ、ファイルをPostScriptに転換
xbasimp(0, "sci1.xps");
unix("scilab -save_p sci1.xps.0 Postscript");
カプセル化Postscript出力もまた利用出来る。
ブロック[1]は、データをディスクファイルからマトリクスに読み込む。
ブロック[2]は、グラフィックウィンドウを(もしあれば)クリアする。重要なのは xbasc() がウインドウに記憶したグラフィックコマンドすべてをクリアすることだ。xbasc() の覚え方は、x11-function, basic level, clear。
ヒント:Scilabのプロットファンクションを使って表示させるとき、プロットファンクションの前にclearコマンドを付けて呼び出すとキイ操作の節約になることが多い
xbasc(); plot2d(...);
と1行にすると、編集のため一度C-pを使って再呼出が出来、またリターンキイを一度押すと再表示される。
ブロック[3] では、enigma が頭をもたげる!やっていることは、
plot2d(set1(:, 1), // x-値のベクトル
set1(:, 2), // y-値のベクトル
-1, // スタイル・インデキス
"011", // 飾り制御
rect = [0, 8e-8, 100, 2e-6]); // プロット寸法
お分かりですか?各アーギュメントの注釈を付けたが、スタイル・インデキスの-1は何を指すのだろう?また飾り制御の文字列の意味は何だろう?
随分沢山詰め込むものだ!ブロック[4] では少し休める。
xtitle([title_line1; title_line2; ...], x_label, y_label)
は、既存プロットに多重線の題名を挿入し、オプションでx軸とy軸にラベルを付ける。
legends([legend_1, legend_2, ...], [style_1, style_2, ...], position)
プロットの position が示す位置に説明を入れる。position = 1 は、北東角を意味する。残りの角は反時計回りに数える。style_i パラメータは、plot2d() 呼出で用いたスタイルインデキスと同じ数字になる。説明文の右にどんな種類のマーカー又は線を引くかを決
最後に、ブロック[5] は、グラフィックウインドウの中のデータを Postscript ファイルに転換する。
xbasimp(0, "sci1.xps")
は、ウインドウ0(我々の唯一のグラフィックウインドウ)に関するグラフィックコマンドを sci1.xps に再表示する。ファンクションの名は "bas" と言う名のX11-daemonに何の働きもしないが、x11 and basic level as xbasc()の語幹から来ている。" imp'' は、フランス語でプリントを意味する" imprimer''から来ているが、"fsck'' は知らない。
ファイル sci1.xps は、有効 Postscriptのほとんどを含むが、全部ではない。内容は、Scilabを用いて処理しなければならない。
scilab -save_p sci1.xps.0 Postscript
が正しい Postscript ヘッダーを加えてグラフィックをペイジ上に再配置する。外部Scilaは unix(``shell-commands'') を用いて呼び出す。
Scilab:三次元関数のプロット
// [1] ファンクションを定義
function z = f(u, v)
// 切頭 Rosenbrock ファンクション
z = 100.0*(v - u.^2).^2 + (1.0 - u).^2
z(find(z > 100)) = 100;
endfunction
// [2] f() に関するサンプリンググリッドを定義
x = linspace(-3, 3, 40);
y = linspace(-2, 4, 40);
// [3] プロットウインドウの内容をクリア
xbasc();
// [4] プロット
fplot3d(x, y, f, 65, 1.5);
// [5] 飾りを定義
xtitle("Rosenbrock Function", "u", "v");
// [6] プロットウインドウの内容をファイルにセーブ、ファイルを PostScript に転換
xbasimp(0, "sci2.xps");
unix("scilab -save_p sci2.xps.0 Postscript; " ..
+ "gzip --best --force sci2.eps");
これがグラフィックのgzipしたカプセル化Postscript 版(eps.gz) である。
Scilab: 二次元ディスクリートデータのプロットの節で新しいことを沢山詰め込んだので、未知のものはブロック[4]にあるだけだ。
fplot3d(x_vector, y_vector, function_of_x_and_y, alpha, theta)
ベクトル x_vector と y_vector が、x_と_y_の_関数 を計算するグリッドを定義する。fplot3d() をOctaveの gsplot と比較すると、Scilab はグリッドを作って呉れるのに気付く。ファンクション fplot3d() は
plot3d(x_vector, y_vector, z_matrix, alpha, theta)
の頭に構築された便利なファンクションで、これは gsplot (and Tela's mesh()) に似ている。
パラメータtheta と alpha は、それぞれ視点の高度とz-軸の周りの回転角度を決める。
Scilab:等高線関数プロット
// [1] ファンクションを定義
function z = g(u, v)
z = exp(u) .* (4.0*u.^2 + 2.0*v.^2 + 4.0*u.*v + 2.0*v + 1.0)
endfunction
// [2] 等高線距離用に加重ファンクションを定義
function y = pow_weight(x, n)
// Map interval X onto itself, weight with N-th power.
d = max(x) - min(x)
y = d*((x - min(x))/d).^n + min(x)
endfunction
// [3] g()のためサンプル採取グリッドを定義
x = linspace(-4.5, -0.5, 40);
y = linspace(-1.0, 3.0, 40);
// [4] X とYが定める点でg()を計算
z = eval3d(g, x, y);
// [5] 等高線距離を計算
iso_levels = pow_weight(linspace(min(z)*1.01, max(z)*0.99, 12), 3.0);
// [6] プロットウインドウの内容をクリア
xbasc();
// [7] 等高線注釈のフォーマットを設定してプロット
xset("fpf", "%.2f");
contour2d(x, y, z, iso_levels);
// [8] 飾りを定義
xtitle("Contour Plot of g(u, v)", "u", "v");
//[9] プロットウインドウの内容をファイルにセーブ、ファイルをPostScriptに転換
xbasimp(0, "sci3.xps");
unix("scilab -save_p sci3.xps.0 Postscript");
出力は印刷準備完了版(eps)でも利用出来る。
ユーザー定義等高線につきOctave/Gnuplotでした苦労を思い出されただろうか?Scilabでは難なくこなせる。 plot3d() と同様、Scilabは
contour2d(x_vector, y_vector, z_matrix, levels)
を取り巻くラッパーとして、便利なファンクション
fcontour2d(x_vector, y_vector, function_of_x_and_y, levels)
を定義するからだ。
然し、 g(u, v) のグリッド上の最大値と最小値を知らなければならないので、fcontour2d() は何の助けにもならない。したがって、ブロック[4]はxとyで定まるグリッド上で g(u, v) を計算する。
z = eval3d(g, x, y)
ブロック[7]では、z とブロック[5]からの iso_levels を使う。xset() 呼出は浮動小数点フォーマット("fpf'') を "%.2f''に設定する。これはすべての輪郭線数を小数点以下二桁に切り捨てるC-printf フォーマットスペシファイアである。f
その他のブロックは全部、既に他のプロットで説明した。
追加のデモプロットは INRIA's Scilab site.にある。
Scilab は、ここで見た以外に沢山のファンクションを持っている。例えば、下記。
・三次元環境のビルディングブロックになる多面体面(例はエングレ・セグレのScigalleryを参照)
Tela
Octave とScilab の項で両アプリケーションともウインドウの従属「状態」をセーブすることを理解した。Gnuplotは最大にそのようにし、 gplot や gsplot は、ほとんどパラメータを持たない。状態は gsetコマンドで制御し、Scilabはプロットウインドウの状態を、例えば説明と題名で記憶する。
Telaはグラフ表示に外部アプリケーションPlotMTV を使う。Telaは反対側の極にあり、そのプロットにはステートがなく、プロットに関する情報はすべて実際のプロットファンクションに呼び込まれなければならない。Telaの方法の利点は(後で見るように hold(on) や hold(off) を呼び出して強制的にそうさせない限り)多数のプロットが互いに干渉しないことである。
既に他のプロットアプリケーションで見たように、PlotMTVマニュアルが手元にある役に立つ。
Tela:ディスクリートデータの二次元プロット
// [1] Read data set_i into N_i-times-2 matrices
set1 = import1("l1.ascii");
set2 = import1("l2.ascii");
set3 = import1("l3.ascii");
// [2] プロットファンクションを定義
function do_plot(d1, d2, d3)
{
hold(on); // hold(off)まで実際のプロットを遅らせる
// set 1を収める
plot(d1[:, 1], d1[:, 2],
"linestyle", 0,
"markertype", 2,
"linelabel", "Set L1",
// 飾りを定義
"toplabel", "Comparison of sets",
"subtitle", "L1, L2, and L3",
"xlabel", "Temperature / K",
"ylabel", "Voltage / V",
// プロット領域を定義
"xmin", 0,
"xmax", 100,
"ymin", 8e-8,
"ymax", 2e-6);
// set 2 を収める
plot(d2[:, 1], d2[:, 2],
"linestyle", 0,
"markertype", 3,
"linelabel", "Set L2");
// set 3 を収める
plot(d3[:, 1], d3[:, 2],
"linestyle", 0,
"markertype", 4,
"linelabel", "Set L3");
hold(off); // プロット!
};
// [3] X11 ウインドウにプロット
do_plot(set1, set2, set3);
// [4] postscript ファイルにプロット
plotopt("-o tela1.eps -printcmd 'cat' -noxplot -print");
do_plot(set1, set2, set3);
大分様子が違う。無状態プロットファンクションを用いて別の仕掛けをおこなわなければならないからだ。Teraは、X11-ウインドウにグラフィックを表示した後、パラメータを「忘れて」しまうので、Postscript ファイルに向けられた出力を用いて何もかもタイプし直さなければならない。コードを写すのは諸悪の根元だから、Teraプロットはファンクションにまとめる。別の作戦は、プロットをX11ウインドウに表示すること、出力ファイルを変更してタイプすることで、これはある程度Gnuplotに似ている。それからもう一度 eps-ファイルにプロットすることだ。
do_plot(...); //X11 ウインドウにプロットを表示
plotopt("-o foo.eps -printcmd 'cat' -noxplot -print"); // 出力を向け直す
do_plot(...); // プロットをカプセル化 Postscript ファイルfoo.eps に収める
グラフィックのカプセル化Postscript 版。
ブロック[2]のファンクションdo_plot が、ファンクションplot() を用いてすべての出力の面倒を見る。Teraの plot() 、事実はTelaプロットファンクション全部、の一般構造は、支配的データアーギュメントを、オプションのキイ値文字列の続くベクトル又はマトリクスとして渡すことである。
plot(x_vector, y_vector // data
"option_key1", "option_value1", // option 1
"option_key2", "option_value2", // option 2
...
"option_keyN", "option_valueN"); // option N
キイ値の対を行毎に置くと呼出全体の読みやすさが増すことに注意。
キイ値文字列自体は自明であろう。linestyle => 0 (= 隠し線) やmarkertype => 2 (= プラス記号) など自明でない組合せはマニュアルを見るか又は利用出来るすべての線スタイルを参照プロットに示したのでそれを参照されたい。
Tela:三次元関数のプロット
function v = linspace(a, b; n)
{
if (isdefined(n)) nn = n else nn = 100;
v = a + (0 : nn - 1) * (b - a) / (nn - 1)
};
// [1] ファンクションを定義
function z = f(u, v)
{
// 切頭Rosenbrock ファンクション
z = 100.0*(v - u^2)^2 + (1.0 - u)^2;
z[find(z > 100.0)] = 100.0;
};
// [2] ファンクション f() からサンプルを取る
x = linspace(-3.0, 3.0, 40);
y = linspace(-2.0, 4.0, 40);
[xx, yy] = grid(x, y);
zz = f(xx, yy);
// [3] プロットファンクションを定義
function do_plot(x, y, zz)
{
mesh(zz,
"xgrid", x,
"ygrid", y,
"toplabel", "Rosenbrock Function",
"xlabel", "u",
"ylabel", "v",
"hiddenline", "true",
"eyepos.z", 2.0)
};
// [4] プロットをX11 ウインドウに収める
do_plot(x, y, zz);
// [5] postscript ファイルにプロットする
plotopt("-o tela2.eps -printcmd 'cat' -noxplot -print");
do_plot(x, y, zz);
system("gzip --best --force tela2.eps");
Tela は、埋め込み linspace() ファンクションを持たないので、急いで一つ決める。
ブロック[2] は、Octave: 三次元関数プロットの節のブロック[2]に似ている。ここではOctaveの meshgrid() がTelaでは grid() に置き換わっている。
三次元メッシュプロット関数 mesh() は、z-値のマトリクスをその第一アーギュメントとして有し、グリッド明細は、次のオプションで与えられる。
mesh(z_matrix,
"xgrid", x_vector,
"ygrid", y_vector);
勿論、z_matrix, x_vector、y_vector のサイズには一貫性がなければならない。
Tela:等高線関数プロット
function v = linspace(a, b; n)
{
if (isdefined(n)) nn = n else nn = 100;
v = a + (0 : nn - 1) * (b - a) / (nn - 1)
};
// [1] ファンクションを定義
function z = g(u, v)
{
z = exp(u) * (4.0*u^2 + 2.0*v^2 + 4.0*u*v + 2.0*v + 1.0)
};
// [2] 等高線距離用加重ファンクションを定義
function y = pow_weight(x, n)
{
// 間隔 X をその上にマップ、N乗で加重
d = max(x) - min(x);
y = d*((x - min(x))/d)^n + min(x)
};
// [3] ファンクションf()からサンプル採取
x = linspace(-4.5, -0.5, 40);
y = linspace(-1.0, 3.0, 40);
[xx, yy] = grid(x, y);
zz = g(xx, yy);
// [4] 等高線距離を計算
iso_levels = pow_weight(linspace(min(zz)*1.01, max(zz)*0.99, 12), 3.0);
il_str = sformat("``", iso_levels);
il_str = il_str[2 : length(il_str)];
// [5] プロットファンクションを定義
function do_plot(x, y, zz, iso_levels_str)
{
contour(zz,
"xgrid", x,
"ygrid", y,
"toplabel", "Contour Plot of g(u, v)",
"xlabel", "u",
"ylabel", "v",
"contours", iso_levels_str)
};
// [6] プロットをX11 ウインドウに収める
do_plot(x, y, zz, il_str);
// [7] postscript ファイルにプロット
plotopt("-o tela3.eps -printcmd 'cat' -noxplot -print");
do_plot(x, y, zz, il_str);
カプセル化 Postscript プリンタ版も利用出来る。
Telaでも、Octaveで輪郭値をGnuplotに渡すときPlotMTVにおこなったトリックを演じる必要がある。し