-xs を用いずにデバッグシンボルを組み込むのを抑制する2013年08月04日 11時20分32秒

動作するプログラムを書くことを出来る人は大勢いるが、正しくコンパイルする事、効率的にコンパイル、リンクをする事が出来る人はほとんどいない。大部分の人達が、自分達が毎日、自分で何を行なっているかを把握していないのだ。知らず知らずのうちに、無駄を毎日の様に繰り返していることもしばしば。少しの変更で、大きな無駄を省けることも多々ある。一人で一割、二割の時間を有効に使えるようになるとしよう。それが、百人に及んだら事は重大だ。

Solaris には -xs というコンパイルオプションがある。これによりオブジェクトファイルなしに dbx でデバッグできるようになる。これを用いると、各々のオブジェクトファイルに入っているデバッグ情報が、実行ファイルへ全てコピーされるのだ。これが、リンクオプションで無いのは若干の不思議。

さて、このオプションを有効にするには -xs を指定する必要がある。これは、コードや中間生成物を共に移さない、本番系に有効なオプションだ。

つまり、このオプションを利用しないと、プログラムのサイズが大幅に小さくなる。元の一割やそれ以下の大きさにまでなってしまった等というのは日常茶飯事だ。

これを省くと、プログラムのリンクの時間が大幅に減る。デバッガの起動と再読み込みの時間も大きく減る。そして、ディスクの圧迫も回避となる。

もちろん、これを使わない不利益は、オブジェクトファイルが必要なこと。何かあった時に心配との事で、常に指定している組織も多い。

逆にいえば、一般的な開発環境では必要無い。そして、本番環境にオブジェクトファイルが無くても、core ファイルを持ってくれば、しっかりとデバッグが出来る。つまり、コードとオブジェクトファイルとしっかりと保持する運用体勢を確立していれば、用いなくても問題はない。または、本番用にのみ指定する運用でもいい。

成果物からは、弘法筆を選ばずの世界なのだが、その過程には、過ぎたるは猶及ばざるが如し、そして、労多くして功少なしの世界でもあるのだ。

なお、-xs は -xdebugformat=stabs を指定し、stabs 形式のデバッグ情報を用いる時に有効になる。-xdebugformat=dwarf で古い型の dwarf 形式を用いている場合には無意味だ。

AIX はある筋の情報によると stabs で、実行ファイル外にデバッグ情報を置くことは出来るが、dbx がオブジェクトファイルからその情報を読むことがまだ出来ない為、似たようなオプションは無いそうだ。

ニューヨークの地下鉄のシステム2010年08月12日 10時58分45秒

今日ニューヨークの地下鉄 MTA で使う、メトロカードの補充をしに駅に降りて行った。

見ると三つ置いてある購買機のうちの一つがチカチカと光っている。よく観察すると、システムが正しく起動できずに再起動を繰り返している。

折角なので、もっと画面をのぞき込んでみた。見ると、NT 4.0 が使われていてメモリが 40MB 程搭載されている。

Stratus 社 の Virtual Operating System2010年06月10日 16時49分58秒

VOS は Stratus Virtual Operating System の略。Multics をモデルに開発された。 Stratus Technologies 社がそのハードウェアと共に提供している。

いわゆる無停止システムで、平均稼働率は五桁の 99.999% を誇る。この確率だと、年間の平均停止時間は五分十六秒となる。この九の後も結構良い数字が並んでいるので、実質的にはもっと短い。

動作中にディスクが壊れたのは何度か目にしたし、CPU が故障したのも見たことがある。が、システムは停止しない。安全を取って CPU のボードを交換していた。

ハードウェアの障害を検知するのも、コンソールの前にはりついているオペレータの人達よりも先に Stratus のサポートの人達が連絡を入れてくる位早い。

安全性という見方では随一なのだが、もちろんそれに伴う不利益もある。最高の性能を引き出すには専用のシステムコールを使わないといけないし、そうなると技術者なども限られて来る。物にもよるが一台ずつの値段も数千万円単位になり、サポート契約も必要になる。

FreeBSD の mount は 7.0 で壊れて、8.0 で更に悪化2009年03月14日 12時14分56秒

UNIX の mount(1) プログラムは、古来より mount_XXX を活用するように作られている。mount が扱えるのは、FreeBSD なら ufs 等といったように、システムのデフォルトのファイルシステムぐらいだ。

数あるファイルシステムを一つのコマンドで賄うのには無理がある。そこで、各ファイルシステムに特化したプログラムをと言った経緯で古い時代の UNIX から、外部コマンドに依存した形になっていた。

大まかな mount_XXX の経緯はこんなところだ。実際に、mount_ を探してみると、手元のシステムでもこんなに沢山の物が出て来る。


% locate mount_ | grep bin
/sbin/mount_cd9660
/sbin/mount_md
/sbin/mount_mfs
/sbin/mount_msdosfs
/sbin/mount_nfs
/sbin/mount_nfs4
/sbin/mount_ntfs
/sbin/mount_nullfs
/sbin/mount_udf
/sbin/mount_unionfs
/usr/local/sbin/mount_fusefs
/usr/sbin/mount_fusefs
/usr/sbin/mount_ntfs-fuse
/usr/sbin/mount_nwfs
/usr/sbin/mount_portalfs
/usr/sbin/mount_smbfs

残念ながら弊害として、似たようなコードが大量にでてくることだ。聞いた話だが、それが嫌でシステムコールの nmount で全てのファイルシステムをサポートしようと作業を 7.0-CURRENT で開始した人がいたようだ。

この作業に三つの大きな問題があった。

一つ目は、その方法。同じ関数や作業が重複しているのは GEOM など他にもある。GEOM も同じように各クラス毎に専用のコマンドがある。GEOM ではライブラリを用いることで、この重複を回避している。実際に、main も含めた大部分が共有化されていて、ライブラリ以前に書かれたものはともかく、最近の GEOM クラスは自前の特殊処理部分を処理するだけに過ぎない。libmount でも作って、似たような方法を取った方が後方互換を保つためにも、作業の独立性を保つ為にも優れていた。

二つ目は非現実的な計画。7-CURRENT で作業が始まったようだが、全然作業が進んでいない事。6.0-RELEASE は 2005 年の 11 月に出たので、三年以上経っている。また、ここ一年でそれらしき、変更を見たことはない。もしかしたら個別にやっているのかも知れないが、現在の nmount 関連は中途半端な状態で放置されている。

三つ目は意図的に非互換な実装をしている事。これらの変更のため、7.0-RELEASE から mount プログラムは外部のプログラムを呼ぶべきかの判定をしている。その実装が、陳腐なのだ。その実装とは、外部コマンドとして呼ぶべきファイルシステムを羅列しておき、それに該当する場合は外部コマンドを呼ぶ。愚の骨頂だ。外部コマンドを全て列挙できるはずがない。実際に、並べられているのは素のシステムで入る nmount がサポートしていない物だけだ。ports から入った mount_XXX はもちろん mount で失敗する。余りにも自明な問題なので、何人かが nmount が扱える物を列挙し、それ以外だったら外部コマンドを呼ぶように提言した。しごくもっともな話だ。システムの正常な起動な部分に関わるところなのだから、後方互換を保つのは当然の話だ。実際にこの問題のせいで、7.0-RELEASE を最初に試した時は、正しく起動すら出来なかった。しかし、そのような提案も気に入らないといった愚かな自負で却下され、現在の惨状に至っている。

そして、最近さらにそれを 助長する変更がされた。現在の mount が正しく外部コマンドを呼べないので、外部コマンドを指定して呼ぼうと言う事らしい。恥の上塗りだろう。そのために、8-CURRENT の mount は mount_XXX を正しく呼べないので、option 部分にファイルシステム型に追加して、わざわざ外部プログラムを呼ぶことを明記しなければいけない。ここには二つの深刻な欠陥がある。本来 mount_XXX を呼ぶのを前提に作られた mount プログラムが mount_XXX を正しく呼ぶことが出来ない。これを形容する言葉が見当たらない実装だ。そして、二つ目はシステムの起動に関する繊細な部分が二つのメジャーリリースにわたって、後方互換のない変更がなされている事。どんな nmount と mount の実装をしようと思っているのかは知らないが、早く以前と同じ書式で正しく動くような形での再実装を終わらせて欲しいものだ。

散々批判したが、パッチを当てれば動くし、正しく動作するシステムを自前で用意するのも無理なので、今のところは FreeBSD から他に移るつもりは無い。

Java Beans について思うこと2008年08月21日 17時56分18秒

Java Beans は Java の規格や実装ではなく、規定だ。単純に言えば、Java Beans は クラスは必ず、引数を取らないコンストラクタを持ち、全てのメンバ変数に Set と Get をする関数を持つことだ。これは、外部スクリプトや、Java インタプリタ内で機械的にクラスを処理出来るようにと決められたルール。コードを自動生成するような時に、簡単にメンバ変数を取得、変更出来るようにとの意図がある。

この規定自体は良く出来た物だと思う。しかし、実装と実現方法が悪い。単純なルールに沿って関数を作るだけの規格なのに、簡単に実現する方法がない。いくつかの Beans 生成のツールなどもあったが、全ての用途に使える物ではない。手で書くことも出来るが、面倒くさいことこの上ない。変数の名前を変えるだけなのに、最低二つの関数の名前を変える事が必要になる。いわばコンピュータの奴隷となるを強いる規定なのだ。しかも、Java のクラス関数の全てを一つのファイルに詰め込まなくてはいけない文法により、実装には直接的に関係ない関数が結構な量の行数を占めてしまう事もある。はっきり言ってコードが見づらい。

似たような実装に Serializable つまり、直列化がある。ObjectStream クラスの readObject と writeObject が重要な役割を果たしている。それゆえ、全てのオブジェクトが Serializable を implements するだけでストリームを通して読み書きできるようになる。もし独自の実装が必要なら、自らその読み書きの方法を書き換える事も出来る。

Beans インターフェイスを Java のコアクラスの一つとして作り、implements をしたクラスをコンパイル時に検出する。そして、実装されている setter や getter はそのまま使い、存在しない物をコンパイラによって生成すれば速度にペナルティを払わずに実現出来る。まあ、インタプリタにて setter や getter を生成することも出来るだろう。

と思い続けて、既に十年ぐらい経つのであろうか。例え自動生成できても、目障りなコード。この現状をどうにかしてくれないかと、再び思う日々であった。

Disk full の場合2008年07月20日 12時39分11秒

ディスクの容量が無くなった時には、何を諦め何を行なうかを決める必要がある。簡単明瞭な回答は無いが、システムが何を行なう為のシステムかをしっかり考え直す事により見えてくる。また、

もう一つ大切なのは、ファイルに書き込むのはレコード単位に行なう事だ。そして、それらのファイルを読む側もレコード単位で処理を行なえる様にする。UNIX を始めとするシステムでは、レコードはアプリケーションレベルで定義するものだが、これをしっかりと決めておく。もし、ディスクに空きが出来た時にレコードを完結できないと、読めないレコードが出来る事になる。また、未完のレコードを読み出し側が処理をしようとすると、処理落ちにつながる。

以下が、個人的に使っている指標だ。

まず、システムのログ、特に統計などの情報は諦める。システムの動作を見る上で役に経つが、無くても動作に問題ないものは切り捨てる。

それに引き替え、重要なデータを記録するプロセスであれば であれば諦める。無いと不便なだけなら、基本的に動作に問題がない。

重要なプロセスであれば一時的に処理を止める。例えば、取引決裁のシステムだとしよう。注文の記録を書き込めないのであれば、決裁の為の情報が保存できない。そうなると、注文を一時的に停止するのが、一番被害が小さくなる。ディスクに余裕が出来れば、注文の受付を再開すればいい。

ファイルを IPC に使うとしよう。異なった OS を連係させる時のデータの移動などに使う事がある。受信側がネットワーク越しに流れてくるデータをファイルに書き込む。この様な場合は、正しい制御が重要となる。もし、ファイルに書き込めないのであば、入力側を一時停止する必要がある。ファイルに書き込めないのであれば下流側のシステムは動作を続けられない。下流側には、主要な機能を置かないようにする必要もあるだろう。

思い付くままに書き連ねたが、システムをより強固にするのに役に立てばと思う。

ファイル出力の要注意条件2008年07月17日 14時40分11秒

ファイルへの書き込みは難しい。一番頭を悩ませるのが、ディスクがいっぱいになった時だ。しかし、これさえ正しく処理が出来れば、後はさほど難しくない。

何らかの入力を受け取って処理をする。単純な Web サーバの様にただ単に情報を表示するだけなら、何もディスクに書き込まなくても、ただ単にリクエストに答えていれば困ることはない。ところが、注文を受け付けられるようにしたらどうなるだろうか。これらの注文を正しく記録できなければ、後々困ってしまう。

この例だと、あまりファイルよりデータベースで処理した方が良いが、何らかの理由でファイルに書き込まなければならないとする。まあ、ファイルで、記録とプロセス間通信の方法を行なっているなどと仮定しよう。ディスクが故障でもするか、ディスク容量が枯渇しない限り、今まで問題なく書き込みが出来ていたファイルに急に書き込めなくなることはない。前者はソフトウェアでは、良い対策が取れない。ここでは、問題にしない。しかし、後者は違う。ここで、どの様な処理をするかで障害時の対応が変わってくる。

特に、一レコードとして書き込まれるはずだったデータが、部分的にしか書き込まれない。もし、誰かがファイルを消したり、プロセスが一時ファイルを消したら、どうなるだろう。tail -f の様にファイルに書き込まれるのを待って、読むプログラムがあったとしたらどうなるだろう。書き込みが成功するまで再試行するとしたら、どの様な弊害が生まれるのだろう。

場合に依るので、簡単な答えは無い。しかし、そのデータの重要性、プロセスの入力先、出力先、障害が起きたときの対応策、利用可能なリソース等を総合的に判断すれば、それなりの回答は出てくる。恐らく、そのような状況になれば、一時的でもシステムを止めなければいけない事になるかもしれない。

堅固なシステムを構築するには、まず止まりにくい設計と実装。そして、正しい運用。最後に、障害からの復旧の早さと容易さが重要になる。

ポインタを無くした弊害の事例2008年06月23日 13時10分13秒

Java はその最初からポインタをプログラマから隠すことに重きをおいてきた。オブジェクト指向言語と言うこともあり、それなりの成功を納めていた。

厳しい環境などではガーベッジコレクションの時期や量などで、問題になるとも聞くが、別に C や C++ でも、ガーベッジコレクションを用いた実装もあるので、メモリの扱い自体が問題ではない。

オブジェクトを用いた高級言語として Java を扱うと、実際にメモリアクセスを必要とする事は無い。しかし、低レベルの操作を行なおうとすると色々と不便な目にあう。例えば、I/O stream 等がその典型的な例だろう。InputStream には二つの read がある。int read(byte[] b) と int read(byte[] b, int off, int len) がそれだ。

C 言語にはこのような二つの read など必要無い。C 言語では、read に読み出すバイト列のアドレスを渡す。それに引き替え、Java では、バイト列のアドレスを直接渡すことが出来ないので、int read(byte[] b, int off, int len) のインターフェイスを用いて、何処に読み出すのかを指定する必要がある。どのみち、必要な変数の数は変わらないが、インターフェスが繁雑になる。

java.lang.OutOfMemoryError extends java.lang.Error2008年06月11日 16時12分51秒

Java の OutOfMemoryError は Error を拡張している。少々長くなるが、以下が java.lang.Error からの引用だ。
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a "normal" condition, is also a subclass of Error because most applications should not try to catch it.

A method is not required to declare in its throws clause any subclasses of Error that might be thrown during the execution of the method but not caught, since these errors are abnormal conditions that should never occur.

Exception は catch 出来るが、Error は catch 出来ない。Java によると Error は「例外処理できない深刻なエラー」とある。しかし、メモリ不足は処理できないエラーではない。むしろ、エラーの処理を正しく処理しようとするのなら、メモリ不足を正しく処理する必要がある。特に無停止系の処理をするのには、このような状況を正しく処理しないのは、むしろ好ましくない。

これを catch 出来ないというのは深刻だ。つまり、何らかの処理が意図しないところで停止し、内部状態が不明な事に陥り兼ねない。確かに、メモリ不足の処理を必要としないプログラムも多い。メモリ搭載量も年々増し、普段はメモリ不足になることも少ない。

そんなに正確無比な処理を必要としなくても問題は起こる。画像処理や、データベースの処理などを WeakHashMap などでキャッシュすることを考えよう。ImageMagick の display の様な画像を表示する場合を考えよう。画像が大きくなると、再描画する時の為に、キャッシュを使いたくもなる。そこで、WeakHashMap を用いて、そこに無かった場合には、画像のアイコンを作成する。もし、OutOfMemoryError が出るのであれば、この時だ。既に画像のキャッシュに大量にメモリを使っていて、新たに画像オブジェクトが生成できない。一時的にキャッシュを破棄すれば、十分なメモリが確保出来る状態になるが、メモリ不足を検知する手段が無い。つまり、表示できるはずの画像が、表示できないことになる。

Java のポインタを無くすという試みは、悪くは無い。しかし、それだからといって、計算機からの警告すら寸断するのは頂けない。使いはするが、Java を好きになれない理由の一つだ。

長期にわたるコードの査読は消耗する2008年05月22日 15時58分49秒

今、潜在的な問題を洗い出す為にコードの査読を続けている。 簡単に探している問題を例えると、二千年問題の様な感じだ。今まで、英語大文字に限定されていた文字列の大きさと許される文字コードを増やす。年号の桁数を二桁増やす必要があったのが二千年問題だ。約十万行超を査読した。

バグの修正や機能の追加程度だったら、大した事無いのだが、システム全域に渡る可能性のある変更なので全てのコードを調べなければならない。また、テストを行う前に全ての変更点を洗い出して、修正してから始めたいとの事だ。これは、ただの無謀でしかないと思う。

元々のシステムのデザインはよく出来ていたわけでもなく、またコーディングの技術も低い。しかも、手軽なシステムやプロセスに関する文書も存在しないので、コードを査読しながら各プロセスが何を行うかを理解しなければいけない。言ってみれば最悪のコード査読環境だ。それなりに古いコードで人の出入りがあったとはいえ、書いた本人たちも判らない部分が多々ある。また、量も多いので長期戦も必須だ。

このような最悪の状況で作業を進めるのは物凄く消耗する。そこで、一日の量も三千行から五千行程に抑えて、余裕をもって作業を進めていかないと終わる前に力尽きてしまう。関数を調べるにしても、呼び出し元と他との関係なども調べていると結構時間をくう時もある。それに加えて、他人の流儀で書いたコードは慣れるのに少々時間がかかる。

元々、他人の書いたコードを紙面だけで点検するのを好きだと言う人は見た事が無い。そして、結果として何も生み出さなかったとしても、手間と精神力を使う重労働だ。数千行程度だったら、それでも一日、二日で終わる。それが、十万行に及ぶ量を査読するとなると何週間単位となる。毎日そんな事を続けていると、日々疲労も蓄積して、早い時など半日が過ぎたぐらいでもコードを見ていても何が書かれているのか分からなくなってしまう事もある。

査読も長い事続けていると疲れてしまう。そうなると、目にはコードが映っているのに理解出来なくなる。文章を読んでいても、個々の単語は理解出来るが、文章が理解出来ない。個々の文字は見た事あるのだが、繋がると文字ではなく紙や画面の染みにしか見えなくなる。そうなると、その後の作業からは何も期待出来ない。

この疲労感が結構強烈で、最近は怠惰感が拭いきれない。