昔の最近の出来事(2011.05)

2011/05/31

日付越え前に帰着。

何やらうまくいかないのを調べたり。

2011/05/30

遅めに帰着。

ちょろりコーディング。

2011/05/29

昼過ぎ起床。

もそもそとコーディング。

Webを検索していると、高校生とかでD言語を使っている人もちらほら居るようです。 しかしなんでまたD言語を?と思わなくもなかったり(^^;; なんとなくD言語関連ページ を検索するとゲームを作っている割合が他の言語に比べて特別に多いように見える のがその要因かな?と思ったりも。でも、実際には2004〜2006年頃に色々作られたのが ピークで、今はそれほど色々作られている感じでは無いような気も。また、相対的に ゲーム以外のものを作っている例が極端に少ないのも錯覚する要因かも。

2011/05/28

昼過ぎ起床。

PSNサービス一部再開。アクティブなフレンドの殆どは既にログイン済みだったり(^^; ストアはまだダメっぽい。

ftpクライアント。Webで拾ったC言語で書かれたソースをcygwinのgccでコンパイルして (MinGWはsocket.hが無くてコンパイルできなかった為)実行してみたのですが、やっぱり POSTを投げたところで突っかかったり。で、gdbで追いかけてみようと した訳ですが、gdb上で実行すると何故か意図通りにリモートファイルの内容を 取得できて正常終了する罠(^^; なんだこりゃ?

なんとなくPOST 文字列の最後が"\n" になっていたのを "\r\n" に変えたら動くようになってみたり。なんでデバッガ上から実行すると動いたのか の方がよくわからなかったりも。

色々間違えてたりバグっぽいものにつっかかったりしましたが、やっと ftpを経由してファイルの内容を読み取ることができたり。以下覚書き。
Webで行き当たった例は アクティブモードの方法しか見つけられなかったのですが、 パッシブモードの方がよさげだったので、そちらを使ってみました。この場合、 少しシーケンスが違ってきます。パッシブモードは大体以下のような感じ。


SocketStreamクラスはStreamクラスが基底クラスになっているので、readLine()を使用する 事もできるのですが恐ろしく遅かったです。このため、ファイルをメモリバッファに一気読み するようにしました。でも、ファイルのサイズが大きいとメモリがGCされないという 問題に突っかかった為、バッファを分割したりとごちゃごちゃいじったり。

そういや、PASVコマンドで返ってくるIPアドレス文字列もPORTコマンドで送るIPアドレス 文字列も「,」を区切り文字としています。でも、IPアドレスを32bitのuintにパッキング するクラスなんかは「.」を区切り文字としたIPアドレス文字列でなくてはならなかったり する為、その為の変換が必要なのがなんだか微妙に面倒臭い感じだなぁと思ったりも。

2011/05/27

早くも無く遅くも無く。

ちょろり調べ事。

2011/05/26

遅めに帰着。

特に進展無し。

2011/05/25

遅めに帰着。

なんとなくsocketが使えるようになったので、VMWareのFTPサーバを相手に Webで参考資料を見ながら接続の練習をしてみたり。でもなんだかうまくゆかず。 ログインはできるのですが、PORTコマンドのレスポンスが返ってこないような気が。

参考資料の一つである inetutilsに含まれるftp.cのソースを眺めてみたり。 意外と大きい(^^; なんとなく受信した文字列のパースにやたらコード量が かかっているような気がしたりも。

2011/05/24

遅めに帰着。

そもそもの問題は、「gdc ftptest.d -lws2_32」みたいにしても、 ws2_32.dll内の関数とリンクできないというものでした。 でも、次のようにしてなんとなく回避できる感じに。


MinGWのCヘッダである winsock2.h では、どの関数もリンケージが Pascalとなっていたので、Dでも同じようにextern(Pascal)を指定して みたりもしたのですが、反応に変化はありませんでした。 そもそも、Cでも -Eでもってcppを通った後のソースを見てみると、 PascalリンケージでもWINAPIリンケージでも(__attribute__((__stdcall__)) に置き換わるので、どっちでも良いような気がしたりも。
socketを使えばスグに気づくレベルなので、特に報告されていないところを 見ると誰も使ってないという感じっぽい。 対処療法的な方法なので、普通に使えるように直してもらえると良いなぁと 思ったり。因みにDMDでは特に何もしなくてもリンクに失敗する事はありません でした。

2011/05/23

遅めに帰着。

先日のwinsock関連のライブラリがリンクできない件を調べたり。 何をやっても反応が無い感じ。うーむ。

2011/05/22

昼過ぎ起床。

もそもそとコーディング。ftpを使ってネットワーク越しのファイルを参照したいと 思い、D言語でftpクライアントってどんな感じに書けばよいか調べたり。 なんだかイマイチ簡単な感じになっていなさげ。perlのftpモジュールくらいに なってれば楽チンなのですが。あと、gdcだとstd.socket内で使用している 関数(winsock2関連の関数)がうまくリンクできなかったり。 いずれにしても地雷が満載という感じです。

早速地雷の中を通ってみることに。まずはwinsock2を使ったC言語の例をWebで 探して、WindowsAPIのラッパー を使い、D言語に移植しながらリンクできるかを試したり。 ところが、次のようなので困ったり。
htons()という関数を使ったとき、この関数の実体がlibgphobos2内の std/c/windows/winsock.d内と、MinGWのライブラリ libws2_32.a の両方に 存在して、multiple definition でリンカがエラーしました。 phobosの方はひとまず使わないので、arを使ってlibphobos内のwinsock.oを 削除してみたのですが、datetime.oがwinsock.oに依存しているようでundefined symbols でエラー。datetime.oも削ってみると、更にそこらじゅうでundefined symbolsに なったり。うーむ。
そもそも、何故WS2_32.dll 内に実装のある関数をlibphobosの中で独自に実装してるんだ? TANEが何か勘違いしているのだろうか?..........

2011/05/21

昼頃起床。

もそもそとコーディング。途中で謎な現象にぶつかりながらもどうにか回避。

2011/05/20

遅めに帰着。

もそもそとコーディング。途中であまりの眠さに死亡。

2011/05/19

遅めに帰着。

ちょろりコーディング。

2011/05/18

遅めに帰着。

そういやEmacsでスクロールバーを使って水平スクロールする事って できないんだっけ?と思ったり。少なくともemacs-22ベースのMeadow3 はダメっぽい予感。Cygwinパッケージのemacs-23で少し調べてみたところ、 toggle-horizontal-scroll-bar なるものが存在していたので、使えるのか? と思って実行してみたら 「toggle-horizontal-scroll-bar: Horizontal scroll bars aren't implemented yet」 とか出てきてガッカリ。そういや、hscroll-mode の代わりには toggle-truncate-lines を使うのが良いっぽい。

2011/05/17

遅めに帰着。

ちょろっと調べ事をして終了。

2011/05/16

遅めに帰着。

Webをちょっと見て終了。

2011/05/15

AM中に起床。

調べものしたりもそもそとコーディングしたり。

ちょろり本屋に。「友達100人できるかな(5)」最終巻。流石に1人/1話で100人分という訳には いかなかったようですが、全巻通して読後感の非常に良い作品だったと思います。

米国と欧州ではPSNが一部サービス再開したもよう。日本を含むアジア圏はファーム ウェアは提供されるもののサービス再開はまだっぽい。

2011/05/14

昼前起床。

ずっこけコードの調査。std/format.d内でsegfaultになっているのですが、 スタックトレースを見るとちょっとひっかかる所があったりも。

Program received signal SIGSEGV, Segmentation fault.
formatArg () at ../../../libphobos/std/format.d:3239
(gdb) where
#0  formatArg () at ../../../libphobos/std/format.d:3239
#1  0x0040be77 in doFormatPtr (putc=..., arguments=..., p_args=0x0) at ../../../libphobos/std/format.d:3828
#2  0x0040c0d5 in doFormat (putc=..., arguments=..., argptr=0x10bfba0 "\005") at ../../../libphobos/std/format.d:2742
#3  0x0040d1dc in bug2479format (arguments=..., argptr=0x10bfba0 "\005") at ../../../libphobos/std/string.d:2090
#4  0x0040d2a6 in format (_arguments_typeinfo=...) at ../../../libphobos/std/string.d:2140
#5  0x004020fd in proc (this=..., hwnd=0x630730, msg=15, wp=0, lp=0) at ProgressWindow.d:202
#6  0x0040233d in _WndProgressProc (hwnd=0x630730, msg=15, wp=0, lp=0) at ProgressWindow.d:243
#7  0x77cf8734 in USER32!GetDC () from /cygdrive/c/WINDOWS/system32/USER32.dll
#8  0x00630730 in _Jv_RegisterClasses ()
#9  0x77cf8816 in USER32!GetDC () from /cygdrive/c/WINDOWS/system32/USER32.dll
#10 0x00402234 in proc (this=..., hwnd=0x0, msg=0, wp=0, lp=6489904) at ProgressWindow.d:229
#11 0x00630730 in _Jv_RegisterClasses ()
#12 0x77d08ea0 in USER32!DefWindowProcW () from /cygdrive/c/WINDOWS/system32/USER32.dll
#13 0x00000000 in ?? ()

#3のスタックの「bug2479format()」って何だ?(^^; bugzillaで調べてみると、随分前に報告されたバグへの対処っぽい。今回は ひとまず関係無しか?
むむ、問題となるformat()を使わないコードにするとずっこけないぞ? format()のバグなの??

以下のようにすると大丈夫になったり。

      string str ;
      synchronized{
	str = format("%3d%%",rate) ;
      }
      TextOut(hdc, barx+barwidth/2, bary+2, cast(wchar*)(toUTF16(str)) , str.length);

format()をsynchronizedでmutexロックする感じ。えー?そういう事?

今回の話をまとめると、


こんな感じ。ふぅ。

そんな訳で、プロ遊の下にMinGWでのgdcビルド手順を まとめてみました。 御参考まで。

そういやDMDの2.053が出ています。GDCに来るのはまだ少し先かな?

2011/05/13

遅めに帰着。

ずっこけコードの調査。そういやGCをdisableにするのを試してなかったので、 試してみたところ変わりなし。なんとなくスタックが壊れているのでは? と思ったり。

調べ事をしてたらあまりの眠さに急速停止。

2011/05/12

少し遅めに帰着。

--enable-tlsを足したgdcのビルドを使って、手持ちソースをコンパイルしたところ、 .tls_common というアセンブラの擬似コードをasが食ってくれなくてエラー。 .tls_commonでWebを検索してみるも、何故か驚くほど情報が少なくて対処方法が 判らず。

パッチを当てたりして結局ほぼ元のまま。ただ、先日の最小再現コードはSegfault無く動く ようになってしまったのですが、元のコードはSegfault位置が変わってしまった為、 再度追いかけ直しかも。でも、thread.dから、std/format.d内に移った為、 もう少し調べ易くなったかも??

2011/05/11

少し遅めに帰着。

色々調べる前にバージョンを新しくしておこうと思い、gdcの新しいのをビルドしたり。 今までのと同じconfigureオプションでビルドするのと、もう一つ --enable-tls を 付けたビルドを行ってみたり。

とりあえずエラー無く終了。で、先日のコードをコンパイルしてみると、Segfaultで ずっこけなかったので、あれ?何か直ってた?と思ったのも束の間、最小パターン じゃないコードではやっぱりダメだったり。

2011/05/10

少し遅めに帰着。

最小パターンのコードを作成して確かめてみたり。 貼り付けるには少し大きいので、ここに置く⇒(ProgressWindow.d) あと、コンパイルにはWindowsAPIの バインディングが一式必要です。

まず、DMDでコンパイル&実行してみると大丈夫っぽい。なので、ずっこけるのは gdc-MinGW特有の何かが影響しているものと思われます。

$ gdc -g -I. -static -mno-cygwin -mwindows -fversion=Unicode -fversion=WindowsXP -fdeprecated ProgressWindow.d

$ gdb -q a.exe 
Reading symbols from C:\cygwin\home\tane\develop\dlang\prog_test/a.exe...done.
(gdb) run
Starting program: C:\cygwin\home\tane\develop\dlang\prog_test/a.exe 
[New Thread 5372.0x14a8]
[New Thread 5372.0x17e0]
[New Thread 5372.0x1284]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 5372.0x17e0]
0x004058a8 in remove (t=...) at ../../../libphobos/core/thread.d:1806
1806                    t.next.prev = t.prev;
(gdb) where
#0  0x004058a8 in remove (t=...) at ../../../libphobos/core/thread.d:1806
#1  0x00405ca2 in thread_entryPoint (arg=0xcb5f00) at ../../../libphobos/core/thread.d:181
#2  0x77bea3b0 in msvcrt!_endthreadex () from C:\WINDOWS\system32\msvcrt.dll
#3  0x7c80b729 in KERNEL32!GetModuleFileNameA () from C:\WINDOWS\system32\kernel32.dll
#4  0x00000000 in ?? ()
(gdb) quit
A debugging session is active.

        Inferior 1 [process 5372] will be killed.

Quit anyway? (y or n) [answered Y; input not from terminal]

$ ../../../dmd/dmd_2.052/windows/bin/dmd.exe -I.  -version=Unicode -version=WindowsXP -d ProgressWindow.d

$ ./ProgressWindow.exe 
run exit

因みに、WM_PAINT内で色々コメントアウトされているのは、DMDでリンクできなかった 為です。あと、69行目〜81行目をコメントアウトして、83行目のMessageBox()のコメント を外すと、これはgdcでもSegfaultしませんでした。

スレッド管理のリストが壊れているので、壊されている所を押さえられれば 良いのですが、ブレークポイントを設定してもスレッドを渡り歩いている感じ になってて、どうやって追いかければ良いのか良くわかりません(^^;

2011/05/09

少し遅めに帰着。

もそもそと調査。ちょっとパターンが掴めたようなそうでもないような。

2011/05/08

昼頃起床。

昨日の調査結果で一点気になる所があったので調べたり。
ptrlistを静的配列ではなくて、動的配列にした場合は問題無くガベージコレクトされたという点。 これ、int配列じゃなくて本当にポインタを保持する為のポインタ配列にしてもガベージコレクト されちゃうんだっけ?という疑問が沸いてきたり。で、 昨日のコードの33行目を「void*[] ptrlist ; ptrlist.length=1000 ;」に、 49行目を「ptrlist[i] = storage1.array.ptr ;」として、動的配列にしたポインタリスト にポインタを保持するようなコードに書き換えてみたところ、ガベージコレクトは されないようでした。 よく調べてませんが、本当のポインタリストにした場合は GCのrootが新たに 追加されているものと思われます。

手持ちコードのgdc 2.052対応。プログレスウインドがうまく動かず。 スレッドを使って非同期に進行をウインド表示しているのですが、 スレッド実行が終了した後、core/thread.d内で終了したスレッドをリストから 外す所で何やらSegfaultになっていたり。gdbでずっこける手前で止めて、 スレッドリストを表示してみると、リストの中身が壊れている為かいきなりgdbが死んだりして どうにも調べが付けられず。

なんとなく スレッドの中でCreateWindowEx()を実行すると何故かスレッドリストが 壊れる予感。

2011/05/07

昼頃起床。

DMDの方でGCに関するバグ報告が挙がってないだろうか?と思い、 digitalmars.D.bugs を探してみたり。ひとまず「GC」という文字列でタイトル一覧を文字列検索したところ、 例えばこんなの(GC.collect() and GC.minimize() not releasing memory) が見つかったり。でも、勘で読み進めているとこの掲示板は正式にバグ報告をする場 では無いようで、Bugzillaでバグレポートは管理されている模様。で、 何か見解が得られているのか見てみようとする訳ですがアカウントが必要らしい。 えー?面倒臭いなぁ? てか、Web検索エンジンとかでも引っ掛けられるように見るのだけはアカウント 無しにして欲しいんですけど。

そんな訳でBugzillaのアカウントを取得して眺めてみる訳ですが、英語がダメな TANEには投稿数が多すぎて読みきれません(^^;;; ただ、上に挙げた掲示板で見つけた 報告はBugzillaには登録されていない模様。もし再現すれば今回TANEが調べたのと同じ原因に 到達できると思われる核心をついたコードだと思うのですが。

DMDでも再現するようなGCにコレクトされない例を作ってみました。

$ cat -n gc_test.d
     1  //
     2  // メモリ割り当ての挙動を見る
     3  //
     4  import std.stdio ;
     5  import std.string ;
     6  import core.memory ;
     7  import std.conv;
     8  
     9  class Storage{
    10    private byte[] array ;
    11    static int no_master ;
    12    int        no ;
    13  
    14    this(int size){
    15      no=no_master ;
    16      no_master++  ;
    17      writef("construct! no=%d\n",no) ;
    18      array.length = size ;
    19    }
    20  
    21    ~this(){
    22      array.length=0 ;
    23      //delete array ; //(1)コレが入っているとガベージコレクトされる
    24      writef("destruct! no=%d\n",no) ;
    25    }
    26  }
    27  
    28  int main(string args[])
    29  {
    30    Storage   storage1 ;
    31    int       alsize  ;
    32    int       loopnum ;
    33    int[1000] ptrlist ;
    34  
    35    if( args.length == 3 ){
    36      alsize  = to!(int)(args[1]) ;
    37      loopnum = to!(int)(args[2]) ;
    38      loopnum = loopnum>1000 ? 1000 : loopnum<=0 ? 1 : loopnum ;
    39    }else{
    40      writef("args  memsize[MB] loop\n") ;
    41      return(0) ;
    42    }
    43    core.memory.GC.collect() ;
    44    
    45    writef("===loop start===============================================================\n") ;
    46    for( int i=0 ; i<loopnum ; i++ ){
    47      int size=1024*1024* alsize ;
    48      storage1   = new Storage(size) ;
    49      ptrlist[i] = cast(int)(storage1.array.ptr) ;
    50      writef("**********array_ptr=%08x\n",storage1.array.ptr) ;
    51      core.memory.GC.collect() ;
    52    }
    53    writef("===loop end=================================================================\n") ;
    54    
    55    return(0) ;
    56  }
    57
$ ../dmd/dmd_2.052/windows/bin/dmd.exe -O gc_test.d
$ ./gc_test.exe 1 500
===loop start===============================================================
construct! no=0
**********array_ptr=00b10010
construct! no=1
   :省略
**********array_ptr=23e50010
destruct! no=497
construct! no=499
**********array_ptr=23f51010
destruct! no=498
===loop end=================================================================
destruct! no=499


実行例では1MBを500回確保しては開放されを繰り返します。タスクマネージャで観察すれば 使用メモリが増え続けるのでより判り易いでしょう。
狙いどころは次のような感じです。GC動作では最初にスタックの値を走査してマーク (使用中である印を付ける)を付けています。そこで、スタックの中に割り当てられる int型の静的配列の中に配列のポインタを記録します。ポインタとして保持している訳では無いので、 storage1のデストラクトと同時にarrayもコレクトされて良い訳ですが、 コレクトされる事はありません。これは単なる値のリストであるptrlistの中身を アドレスとみなしてマークを付けている為です。
例えば49行目を「ptrlist[i] = 0 ;」のように書き換えると、 コレクトされるようになります。array_ptr=の表示を見ると、同じアドレスが何度も 登場することから再利用されている事が確認できます。
また、33行目を「int[] ptrlist ; ptrlist.length=1000 ;」の様に動的配列に変更 してもコレクトされます。動的配列はスタックに保持していない為、配列の中の値が マークに使用される事が無いからと言えます。
ただし、23行目のコメントを外してdeleteを明示的に行えば開放されるようです。 これについては動きは見れていませんが、マークとは別に開放済みである事を示すリスト がありますので、そちらの方に印を付けているからかなぁ?と推測します。

関数呼び出しを行っていれば関数の戻り時にそのスタックは無くなります。 そうすると今回の例のようなリストも当然消滅する訳ですから、そこでGCが走れば メモリは回収されると考えられます。有象無象のスタックを手当たり次第にスキャンするという のはなんとなく構造欠陥っぽい感じもしますが、これは仕方の無い仕様という気が したりも。D言語のGCは万能では無いという事を心に留めて使えって事なのでしょう。

Bugzillaの投稿の中に「Slow GC with large heaps」なるタイトルのバグ&パッチ報告 がありました。性能改善パッチのようだったのですが、フルソース(gc/gcx.d)が置かれていたので なんとなく入れてみたところ、以前動的配列の実体が勝手に 開放されてしまうというバグ(結局原因不明で放置されてた)が発動されなくなったり。 結構派手にパッチがあてられているので、何故OKになったのかは良くわかりませんが (たまたま大丈夫になっているだけの可能性もある)、ひとまずこれで使ってみる事に。

一応、原因不明かつ対処方法不明という案件は無くなった気も。

Webで調べたら、今回調べたPhobosのGCの方法は、一般的に 「ConservativeGC(保守的GC)」 って言うんだそうな。勉強不足でスマソ(^^;;;

2011/05/06

早くも無く遅くも無く。

GCでのメモリ割り当てには、gdc-MinGWでは WinAPIであるところの VirtualAlloc()を使用しています。VirtualAllocのフラグに MEM_TOP_DOWNというのがあり、これを使用するとできるだけ上位のアドレス でメモリを割り当てるという事なので試してみたり。
実際には上位のアドレスといっても0x80000000よりは小さいアドレスで 割り当てられるようですがひとまずいけるかな?という感じ。でも、 あまり芳しくない結果になったり。よく見れてませんが ポインタリストの中には何やら残骸的な物も含まれていて、それらに ヒットしてしまう感じがしたりも。どういう時にスタックに積まれる 場合があるのかは判りませんが、例えばコンパイル時の最適化レベルを 変えたりすると、うまく動いたり(または動かなくなったり)する場合が あるのでは?とも思ったり。うーむ。
オリジナルのDMDでは巨大動的配列のガベージコレクトは 大丈夫だった訳ですが、 GCのコードはgdcと基本的な部分は同じです。一体この問題をDMDの方では どうやって回避している事になるんだろう?

あまりの眠さに急速停止。

2011/05/05

昼頃起床。

GC調査。ぼんやりソースを眺めていたところ、なんとなく流れがつかめたような そうでもないような。 core/thread.dというソースの中にthread_scanAll()という関数があり、この中で メモリ領域をスキャンして使用中か否かをマークしているというのは昨日までの感じ。 で、thread_scanAll()内では、スキャンする領域がWindowsがプラットフォームの場合は 3種類あるようです。 一つ目はスタック領域(これは全スレッドで共通?)、二つ目は各スレッド毎の TLS領域、三つ目は各スレッド毎のレジスタ退避領域(っぽいもの)。Windows以外では 三つ目の領域退避は不要なようです。で、何故か使用中マークが付けられるのは 一つ目のスタック領域のスキャン結果という事が判ってみたり。

Linuxのと動きを比べながら調べていたのですが、何故か全然違う動きになってしまってて、 比較がうまくできなかったり。

んー、なんとなく徐々に判ってきたような気がするようなそうでもないような。 まず、gcx.d内 Gcx.mark(void *pbot, void *ptop)の動きから。ptop〜pbotの範囲を ポインタリストとみなして、リストの各要素がもしメモリプールの中を指している ならば、そのアドレスは有効として使用中にマークするという 動作を行っているようです。ちょっとひっかかるのは、このポインタリストはメモリ アドレスを指していない値がが含まれている点です。実際の例を挙げてみます。
例えば、動的配列を割り当てるとメモリ上は「配列サイズ,実メモリへのポインタ」 の並びになっています。

(gdb) print/x &strage1.array
$5 = 0xcc4ca8
(gdb) print/x 0xcc4ca8
$6 = 0xcc4ca8
(gdb) x/4x 0xcc4ca8
0xcc4ca8:       0x03200000      0x01400010      0x00000000      0x00000000
                ~~~~~サイズ     ~~~~~ポインタ
(gdb) print/x strage1.array.ptr
$7 = 0x1400010

上の例では、50MBの動的配列となってます。で、この配列をGCのマーク処理に食わせたとします。 すると、マーク走査するポインタリストの中に次のような形で入ってました。

0x22fc64:       0x00000008      0x0022fce8      0x004015f5      0x00cc4c80
                                                                ~~~~~~~~~~
0x22fc74:       0x03200000      0x01400010      0x00000063      0x00cc1e00
                ~~~~~~~~~~~~~~~~~~~~~~~~~~

ここからがポイントです。mark()処理では、この何が入ってるか判らないリストを 「全てアドレスとみなして」走査しています。 つまり、動的配列の情報を示す0x00cc4c80も、サイズである0x03200000も配列実体への ポインタである0x01400010もです。ここで問題なのはサイズである0x03200000をメモリ へのポインタとして扱っている点にあります。問題となっている具体的な例を更に示します。
今のインスタンス strage1への参照を無くしたとします。

(gdb) print/x &strage1.array
$10 = 0xcc4c88
(gdb) x/4x 0xcc4c88
0xcc4c88:       0x03200000      0x05f20010      0x00000001      0x00000000
(gdb) print/x strage1.array.ptr
$11 = 0x5f20010

サイズは50MBのまま、実体へのポインタを新たに割り当てた形になります。参照が無くなったので、 先ほどまでの 0x01400010〜 は宙に浮いたメモリ域になります。これを再びGCのマーク処理に 食わせると、

0x22fc64:       0x00000008      0x0022fce8      0x004015f5      0x00cc4c60
                                                                ~~~~~~~~~~
0x22fc74:       0x03200000      0x05f20010      0x00000063      0x00cc1e00
                ~~~~~~~~~~~~~~~~~~~~~~~~~~

のように変わって、元の0x01400010は無くなっています。ですが、これは既に問題のある 状態に陥っています。0x01400010を先頭とした50MBの範囲のアドレス範囲は、 0x01400010〜0x0460000f となります。ところがこのアドレス範囲は 50MBのサイズとして示される 0x03200000をアドレスとしてみなすと含まれてしまうのです。つまり0x01400010を先頭 とした50MBのメモリブロックの途中の領域(0x03200000)を指している人が居るように 見えてしまっている為、開放してはならないという動きになっているようなのです。
これは、動的配列のメモリサイズが小さいと発動しないというのと連動します。例えば せいぜい1MBくらいのサイズだとすると 0x00100000 で、これだとmark()内で有効な メモリ領域範囲外になるので、誤ってマークに使用される事はありません。

(gdb) print/x this.minAddr
$17 = 0xcc0000
(gdb) print/x this.maxAddr
$18 = 0xaa38000

以前、メモリ割り当てサイズが大きいと ガベージコレクトされないのに、サイズが小さいとガベージコレクトされるというのは なんとなく気づいていたのですが(それを回避する為に 明示的にdeleteをするなどと いうワザを発明したりとか)、 今回の動きとピッタリ挙動が連動するという感じかも。つまるところ、ずっと前から バグってたって事になるのかも知れませんが(^^;;;;;

さて、これってどうやって直せば良いんでしょう?そもそもアドレスと見なしては ダメな値をアドレスとして見なしてるのがマズいのですが、それを含まないような ポインタリストってどうやって作るんだ?という感じ。
Linuxの場合はこのポインタリストが非常に大きなアドレスに存在する為、小さい メモリを扱ううちは大丈夫っぽいです。minAddrが更に小さくなる事が無ければ 恐らく問題無いですが、大量のメモリを使ううちにminAddrがどんどん小さくなる ような場合があると、どうでも良い値が 使用しているメモリアドレスに被る可能性が 発生し、それだとうまくいかなくなるという感じかも。
ポインタだけのリストがあれば完全なのですが......

2011/05/04

昼前起床。

GC調査。Linuxの方でgc/gcx.dを調べたり。コンパイルオプションに -fdebug=COLLECT_PRINTF 指定して、元々仕込まれているコレクトに関するデバッグプリントを活かしてみたところ、 fullcollectで領域開放されていることが判ったり。つまり、何かの閾値がある訳ではなく、 単純にコレクトが効いているから、メモリ割り当て量×2くらいで使用量の最大が自然に 決まっているという感じのようです。
一方 MinGW-gdcの方で同じようにデバッグプリントを見てみたところ、fullcollectは 実行されているものの、ちっともメモリを開放していないようです。 なぜコレクトが効いていないのかが謎。プラットフォーム依存のコードは通っていないように 思えるんだけどなぁ?

LinuxとMinGWとを比べながら動作を追いかけてみたり。gcx.d内でpool.mark.test()なる 関数で空きページか否かを判定して、空きページであればメモリを開放するという動作 を行っているようなのですが、MinGWでは何故かマークのテストがいつも使用中を返して しまう為、メモリ開放コードを通らないという動きになっていたり。その使用中マークをどこで 付けているのか追いかけてみるとthread_scanAll()なる関数で行われていたり。 むぅ、またthread関連ですか?少し追いかけてみるもどこでセットされているか良くわからず。

ちょっとずつブレークポイント位置を変えながら値の変わる前後を絞りつつ、最後はwatchポイントを 設定してみたところ、なんとか触っているコードに辿り着いたり。

(gdb) watch *(int*)(0xc10024)!=0
Hardware watchpoint 8: *(int*)(0xc10024)!=0
(gdb) c
Continuing.
Hardware watchpoint 8: *(int*)(0xc10024)!=0

Old value = 0
New value = 1
0x0043adba in testSet (this=..., i=0) at ../../../libphobos/gc/gcbits.d:178
(gdb) where
#0  0x0043adba in testSet (this=..., i=0) at ../../../libphobos/gc/gcbits.d:178
#1  0x00436620 in mark (this=..., pbot=0x22f974, ptop=0x230000) at ../../../libphobos/gc/gcx.d:2259
#2  0x0041a561 in thread_scanAll (scan=..., curStackTop=0x22f974) at ../../../libphobos/core/thread.d:2583
#3  0x00436a58 in fullcollect (this=..., stackTop=0x22f974) at ../../../libphobos/gc/gcx.d:2418
#4  0x00436755 in fullcollectshell (this=...) at ../../../libphobos/gc/gcx.d:2326
#5  0x00435d17 in bigAlloc (this=..., size=52428817, alloc_size=0x22fb98) at ../../../libphobos/gc/gcx.d:2024
#6  0x0043125e in mallocNoSync (this=..., size=52428817, bits=10, alloc_size=0x22fb98) at ../../../libphobos/gc/gcx.d:500
#7  0x00430de4 in malloc (this=..., size=52428817, bits=10, alloc_size=0x22fb98) at ../../../libphobos/gc/gcx.d:414
#8  0x0041b484 in gc_qalloc (sz=52428817, ba=10) at ../../../libphobos/gc/gc.d:207
#9  0x0040c216 in _d_arraysetlengthT (ti=..., newlength=52428800, p=0xcc4c88) at ../../../libphobos/rt/lifetime.d:1326
#10 0x004013bf in __ctor (this=..., size=52428800) at mem_test2.d:18
#11 0x004015f2 in main (args=...) at mem_test2.d:51
#12 0x0040d4eb in runMain () at ../../../libphobos/rt/dmain2.d:529
#13 0x0040d54e in tryExec (dg=...) at ../../../libphobos/rt/dmain2.d:480
#14 0x0040d658 in runAll () at ../../../libphobos/rt/dmain2.d:544
#15 0x0040d54e in tryExec (dg=...) at ../../../libphobos/rt/dmain2.d:480
#16 0x0040dcf8 in _d_run_main (argc=3, argv=0x3e2c70, main_func=0x401474 <main>) at ../../../libphobos/rt/dmain2.d:554
#17 0x00401944 in main (argc=3, argv=0x3e2c70) at ../../../libphobos/rt/cmain.d:5
(gdb)

んー、でも各関数内ではやってる内容が小さすぎて全体の動きのどの辺を担っているのかが良くわからなかったり。 もう少し言い換えると、値をセットしてるのは判ったが単純に値をセットする関数であるため、そこだけ 見てもそれが正しい動きの元に実行されているのか否かが判断できないという感じ。 しかしながら、やっぱりこの辺は プラットフォーム依存のコードでは無いように思えるのですが.....

その後、もそもそいじるも追い詰められず。

「ONE PIECE(62)」。キャラ出過ぎ(^^; そして大量のネタ振りが行われている感じ。
何故か入手できなかった「それ町」の8巻をゲット。いつも様子を見ていた本屋で入手 したのですが何故か初版本。置かれてなかったのは何だったんだ?

2011/05/03

昼頃起床。

まも呪。ボチボチ。ちっとも上達しない気が。

そういやInkscapeのSVGパーサーってどう書かれているんだろう?と思い、 ソースを眺めてみたり。んー?なんだかコードジェネレータ的な物を使って ないような気が....?手書きなのかしら?

もそもそとコーディング。途中GCのバグと思われるものが発動したりして 困ったり。そういや、以前Phobosの GCがイマイチで、それが2.052対応のgdcだとどうなってるかを 再度確認した時、あまり変わってないなぁ と思った訳ですが、dmdのオリジナルでは確かめた事が無かったなぁと思ったり。 で、試した訳ですがどうやらdeleteで明示的に動的配列を開放しなくても OOMにはならない模様。 また、確認環境が無かった Linux(on VMWare)の方でも2.052対応のgdcで 確認した所、こちらもOOMにはなりませんでした。ただし、明示的にdeleteを 入れた方がヒープの広がり具合は小さいので、チューニングのために使うのはアリっぽい。
そんな訳で、動的配列を明示的にdeleteしないとOOMになるのは MinGWのgdc特有の問題だったっぽい。

なんとなくGCを調べたり。OOMになるのは phobos特有の話だと思っていたのですが、 MinGWのgdc限定となると話は別です。Linuxでメモリの具合をモニターを使って 見ていたところ、適当なサイズを上限に再利用が効いている感じでした。 DMDのオリジナルでもプロセスサイズの上限に達するよりも大分前に再利用が 効いている感じでした。そもそも一体何を再利用発動の閾値としているのだろうか? と思ったり。GCの本体コードであるgc/gcx.dはプラットフォームの違いによるコード 切り替えは無いようなのにも関わらず、何故このように動きに違いが出るのかが よく判りません。

少しデバッガで動きを眺めてみたところ、gcx.bigAlloc()が失敗しているようなの ですが、何故だかブレークポイントがうまく設定できなくて、失敗が確定するまでの 経路を特定できなかったり。むー。

2011/05/02

早くも無く遅くも無く。

まも呪。昨日は全然ダメだったステージも、一発目ならうまくいく不思議。でも続けてやると だんだん伸びなくなる罠。

眠くて死亡。

2011/05/01

昼過ぎ起床。

まも呪をボチボチ。集中力が切れると連続死する為伸びず。

PSN不正アクセスのその後。何やらFBIに調査依頼したとか大事になっている気も。 まぁでも、これだけ大規模なサービス停止となると、損失は莫大なものになると 想像されます。もし犯人が居るのであればここはしっかり捕まえて、 誰も得をしないという事を知らしめて欲しい所なのかも。


TOP PREV