最新

おおいわのこめんと (2009-08)


2009-08-01

[Work] 続 開発中

前回のエントリ からもうそろそろ1月ですね。 ほぼ、当初の思惑通り構造改革が終わりつつあります。性能も平均するとちょっとは良くなってそうでなにより。 make test も通るんだけど、なんとなく「これだけ派手にいじったからもうちょっとテストして安心してから出したいなぁ、もうちょっと改良したいなぁ」とずるずるという感じでもあります。

いじっていて気がついたこといくつか。

  • GCC って、
    #define a(b) A
    a
    #line 5 "test"
    (b)
    
    というスタイルのマクロを展開しないんですね……。どうやら C99 的には正しいみたいですが、 Fail-Safe C 側は出力コードがマクロ呼び出しとインライン関数のどちらに最終的になるかは 意識していない(一部を除き runtime 側で変えていいことにしている)ので、マクロの時だけ動かない、 だと困るんですね。*1 いじっていたら突然プログラムが通らなくなったので「あれー」と思ったら……。仕方がないので対策しました。
  • GCC (少なくとも 4.1.2) で複素数型を使うと、__real__ x に代入できるんですね……。 なんで気がつかなかったんだろう……。 fat pointer の表現として long long を使っていて、かつインラインアセンブラを使っている IA32 版だと平均的には若干の違い*2だけど、 対応する 128bit 整数のない 64bit 環境では大いに使えそう。
    ちなみに、__real__ x__imag__ x に代入して x を使おうとすると、「x を未定義で使ってるかも」と 警告されます (^^; x に代入してから虚数部を __imag__ x で上書きするのが正解っぽい。
  • 相変わらず性能が出力コードの変更に対して暴れ馬で困ります。上の複素数の変更も、アセンブラレベルでは良くなっている ように見えるし、全体傾向としては Bytemark ベンチマークで 1% 位良くなっているのですが、 内訳は 10% 良くなったもの (複数テスト) からほとんど変わらないものまであり、困ったことに 14% 悪くなったもの (1つ) が あったりします……。さすがにベンチマークレベルだと規模的にアセンブラコードを追えない*3ので、原因がいまいち判っていないのが困ったところ。それでも最終的にはベンチマークの性能で 結果を出すしかないんですけど、コンパイラオプション1つ変えただけで性能の傾向が変わりそうなのも恐いところです。

*1 自分でコンパイラを作っていると、「注釈+継続行処理」+「include+行番号処理」→「マクロ処理」 という順番で処理するのが自然に思えているのですが、実際は「注釈+継続行処理」→「マクロ+include+行番号処理」なんですねぇ。 ディレクティブをトークンとして定義してしまったからだと思うのですが。

*2 インラインアセンブラを使っていない非 IA32 環境だともうすこし効くかも.

*3 バックエンドの gcc は最適化最高モードでコードを吐いているので、出力アセンブリコードはぐちゃぐちゃです。最適化の一貫なのか、元のソース配置から想像をつかないようなコードの再配置をしてくれます。それでも昔は fib や qsort, 今だと nqueen 位までは頑張ってアセンブラ読むんですけどね。


2009-08-12

[Work] Fail-Safe C 1.6 へ向けて...

今年度末 (?) の version 2.0 リリースへ向けて、次の段階に踏み出す前の準備として いろいろと準備しているわけですが、そろそろ形になりつつあります。 Version 1.5.1 とするには (僕の気分的に) 一杯変更しているので、せっかくだから 1.6.0 にしようかな、という感じ。

とりあえず現状で

  1. 内部中間言語と構造の見直し
  2. 内部データ表現型の見直し
  3. リンク時型情報の変更
  4. C の昔ながらの未初期化グローバル変数の取扱(複数宣言可で1つに統合される)の模倣
  5. 標準ライブラリの特別扱い廃止へ向けての準備(一部の関数だけ独自定義関数での置換えが部分的に可能に)
  6. 一部のライブラリ関数などで必要な追加ネイティブライブラリの自動リンク
  7. ソースの行番号情報の出力への反映
  8. アクセス検査の最適化の実装第1弾
  9. リンカでの型情報生成の最適化 (OpenSSL バイナリが 7% 小さく)

といった辺りが変更予定です。1.0 → 1.5 は対応アーキテクチャの拡充とか研究プロジェクト要素が 多かったのですが、1.6 はその合間で溜っていた懸案を解決するリリースになりそうです。

1. は前に日記で書いた内部構造の変更で、最適化の実装とかの見通しがよくなったので 将来展望が大分明るくなった(?)感じ。一応これが 64bit 対応化準備のメインの部分です。 2. も今よりは 64bit 化の準備の意味合いが大きいかな。

3. だが、最初にアドホックに作った型情報フォーマットを、将来的に拡張可能なフォーマットに変更しました。 まぁバージョン間でのバイナリ互換性は現状ではあまり重要ではない(生成コードの方を変更して互換性がなくなる ことの方が多いので)のだが、いろいろな情報を付加して将来のモジュール間最適化とかにも使える他、 以下の 4, 5, 6 などでも既に使っています。*1

4. は確か Postfix か何かで問題になったコードで、

a.c:
  int x;
b.c:
  int x;
c.c:
  extern int x;

みたいに書いた定義を重複定義で拒否するのではなく、1つの定義にまとめるような実装を作りました *2。 今時の新しいコードではちゃんと extern と 1つだけの実体定義で書いてあると思いますが、 結構現役で使われているコードでもこの書き方があることがまだ多いみたいです。

5. はなんだっけ、qmail かな? (うろおぼえ) 現状では標準ライブラリを1つの大きなモジュールのような扱いにしているために、 fopen は標準のものを使うけど strncpy は自前で定義するみたいなコードがあると 重複定義で落ちるという問題。まぁ C99 ではすべての標準関数は単独でリンクできないといけないそうで、 それを真面目にやるには実際のライブラリ実装の方のモジュールわけをもっと細かくしなければならなくて 頭が痛いのですが、とりあえずそのための機構の準備はできました。あとはライブラリ実装を少しずつ改良していきます。

6. は Linux で crypt(3) が輸出規制のために別ライブラリになっている、という話で、 -lcrypt をネイティブリンカに渡したい、という話。現状でも -Wl,-lcrypt とか書けば 通るし、crypt だけだったら常時リンクでもいい (-lm は今でも常時リンクの扱い) のですが、 将来他のライブラリの wrapper を書いた時のことを考えて、今回きちんと機能を追加しておきました。

7. は #line を出力に入れるだけ……と書けば簡単ですが、実際は内部のすべての処理で 行番号情報を受け渡し続けないといけないので、機械的な書き換え箇所は今回の変更の中でも 1. と並んで多いです。 まぁコードをかなり書き換えている Fail-Safe C で、行単位のデバッガがどれだけ使えるかは疑問ですが、 少なくとも gcc の上でプログラムを走らせてアクセスエラーが出た行番号を捕まえるくらいには 使えると思います。OCaml のライブラリの仕様とにらめっこして、ちょっと格好いいコードが書けたので 自分では満足です :-)。

8. は以前から日記に書いている最適化。ちゃんとデータを計らないといけないですね。 1. と一緒にやりました。

9. も実は僕の心の中だけでは (リンカの論文にはちょっと書いてあるのですが) 以前からの懸案でした。 今の Fail-Safe C のリンカはリンク時に「同じ型である必要がある」型を全部 unify しているのですが、 実はそれだと「実際は同じなんだけど同じ必要がない」型は unify されないのですね。 実装方法を考えていたのですが、ちょっとアイディアを思いつく→書き始めてみる→書きながらどんどん アイディアが改良されていく→書き終わる頃には非常にあっさりと書ける、のコンボがまた発動しました。 で、あっさりし過ぎてなんで悩んでいたのか判らない、というのも前回のリンカのアイディアの時と一緒。 もちろんチェックはちゃんとやっていて、「似てるけど非互換な型」は unify されません。 最小性の証明はできそうにない(最小がそもそも定義できるのかも怪しい)ですが、実用上は問題ないはずです。 結果ですが、OpenSSL で構造体型の数が 30 % 減って、出力が 7% 小さくなりました。まぁこんなもんかな。 これ以上小さくするには、表引きとかを使って「違う動作をするコード」を統合しないといけない感じですが、 性能とのトレードオフの問題が生じます*3

……で、一通りのコードが書き上がったのですが、出来上がってみて1つ大きな問題点が。 困ったことに最低サポート環境(=開発環境)の Debian etch で OpenSSL をコンパイルすると、 GCC 4.1.2 が internal error で落ちまくるという予想外のトラップが……。 昔から Fail-Safe C の開発中は割と GCC の internal error をよく見る感があった*4のですが、 エラー報告の場所も関数の先頭とか謎な場所になってしまっていて手に負えない (;_;/。 ↑のサイズ測定したバイナリも、コンパイルが通らない関数だけ手動で最適化弱めて通した結果だったりします。 僕の予想ではどうやら関数を自動的にインライン化しようとして落ちていると見ているのですが、 -O3 では駄目で -O2 だと通るファイルとか、-O2 でも駄目で -O1 じゃないと通らないファイルとか…… *5。 自分でどうにもならないところに思わず発生した showstopper に、ちょっと頭を悩ませています (;_;/

さすがに etch も GCC 4.1.2 も古いので、開発環境の方を新しくする(=現環境のサポートを打ち切る*6)のも ありかとは思うのですが、どうしようかなぁ……。lenny の GCC 4.3.2 で試して落ちないようなら*7、近いうちに preview 版を出して様子見るとかするかもしれません。

さて、あとはこの日記の内容を「ちゃんとしたドキュメント」にする、というのもやらないといけないのかな……。

追記 (8/14)

えーと……、結局、環境の置き換えはやることにして、 とりあえず gcc が落ちずに成功するまで自動的に最適化を弱める機構を入れてごまかしました(笑)。 全体のコンパイル工程を管理している Perl スクリプトをいじって実現。なんという場あたりな :-)。

インライン化が怪しい、という読みは正しかったようで、-O2 で落ちずに通るファイルに関しては -O3 -finline-limit=10 でも通ることもわかってきました。副作用として、 昨日書いた「-g をつけると落ちるファイルがある」問題も、-g をつけて試してみて だめだったら外す、という同じ戦略を取ることができるようになったので、 最終出力バイナリにも行番号が反映されるようになって 7. の行番号改良の価値が大きく上がりました。 次期 version 1.6 ではメモリアクセス違反があると、通常モードでコンパイルしたバイナリでも

% ./nqueen.safe 11
1357946  
--------------------------------
Fail-Safe C trap: access out of bounds
  Address: 0x805e4a0 + 40
  Cast Flag: not set
  Region's type: int (VS 4, RS 8)
           size: 40 (FA 40, ST 40)
           block status: normal, no_user_dealloc, no_dealloc

backtrace of instrumented code: ...(中略)... #5 0x0805a4eb in write_word_remainder (base0=134603904, ofs=3216115688, val=0, ty=0x805f9c0) at remainder.c:101 #6 0x0804b2a7 in FS_FPiii_v_nqueen (FAB_1b=134603904, FAV_1b=0, FAB_2size=0, FAV_2size=11, FAB_3d=0, FAV_3d=10) at nqueen.c:16 #7 0x0804b4cf in FS_FPiii_v_nqueen (FAB_1b=134603904, FAV_1b=0, FAB_2size=0, FAV_2size=11, FAB_3d=0, FAV_3d=9) at nqueen.c:28 #8 0x0804b4cf in FS_FPiii_v_nqueen (FAB_1b=134603904, FAV_1b=0, FAB_2size=0, FAV_2size=11, FAB_3d=0, FAV_3d=8) at nqueen.c:28 ...(中略)... #16 0x0804b4cf in FS_FPiii_v_nqueen (FAB_1b=134603904, FAV_1b=0, FAB_2size=0, FAV_2size=11, FAB_3d=0, FAV_3d=0) at nqueen.c:28 #17 0x0804b671 in FS_FiPPc_i_main (FAB_8argc=0, FAV_8argc=2, FAB_9argv=157364128, FAV_9argv=0) at nqueen.c:38 #18 0x0804b87c in FG_main (FAtptr_b=0, FAva_b=157379944) at nqueen.c:35 #19 0x0804be30 in main (argc=2, argv=0x0) at main_bootstrap.c:77 -------------------------------- ;2468Abort

のようにきちんとエラー原因の元ファイルでの行番号が追跡可能です。棚からぼたもち的改善 :-)。

*1 あと、バージョン間で非互換なバイナリを間違えてリンクするとエラーになるように準備したのも地味に重要。

*2 Fail-Safe C だと変数の頭についた検査用ヘッダを初期化しないといけないので、「未初期化変数として出力してあとは gcc 任せ」というわけにはいかないくて、1つの定義だけ作るようにリンカで面倒を見ないといけないのですね。

*3 もちろんコードが小さくなってキャッシュに載って速くなる、という可能性もありますが、少なくとも「すべての場合に+」な最適化にはなりそうもないです。

*4  昔はデバッグ用に -O6 -g とか指定していた(できていた)のだが、 gcc 3.3 にした辺り (?) からこの組合せがあまりに落ちまくるようになったので、今は -O6 だけになっている。 -O6 になっているのは、「-O3 以上は変わらないんだっけ?」と思いつつも惰性。

*5 いまさら 4.1.2 のバグレポとかしても誰も嬉しくないだろうしなぁ…。

*6 研究環境・開発環境を再構築するのに手間がかかり、仕事が忙しいのにとても、というのが環境移行をしない一番の理由なのですが、まぁ論文出したあとなのでチャンスといえばチャンスなのかもなぁ。

*7  ああ、そういえば lenny のテスト環境もハードウェアトラブルで死んでるんだった (;_;/ 復旧までの代わりの環境を構築しないと……。


大岩 寛 (おおいわ ゆたか) <yutaka@oiwa.jp.nospam ... remove .nospam> .

Copyright © 2005-2014 Yutaka OIWA. All rights reserved.
Posted comments and trackbacks are copyrighted by their respective posters.

記事の内容について (Disclaimer / Terms and Conditions)