第D0回 DirectMusicに挑戦する(会社はじめました…!?) 00/11/06
ちょっと税金の高さにたまり兼ねて、会社を始めることにしました。何であたしゃサラリーマンの給料ぐらいの税金を払わにゃならんのですか。そんなもうかってるわけでもないのでそげにいじめんこってええですたい。(誰やねん、お前…)
それはともかく、会社の名前は、「トンネル商事」にしようかと思っていたら、周囲からは猛反対される。トンネル商事は、ダメなんかー(笑) トンネル商事代表取締役やねうらお うひょーかっこよすぎ!(めっちゃ怪しいってば…)
仕方ないので別の名前で申請することにして、会社のハンコがいるので、発注。それはともかく、やねうらおの仕事机に「かいしゃ、始めました」のノボリを立てる。ローソンとかに立ててある「おでん、始めました」と同じような奴だ。周囲からは、「あんたにとって会社は、おでんと同じレベルなんかー!」と突っ込まれつつも、とりあえず始めたんだってば(笑)
合資会社の場合、自分は無限責任社員になるわけで、誰かひとり有限責任社員が必要になるので、知り合いに頼もうと電話をかける。
お久しぶりー!元気にしてっかー?
「やね師匠!聞いてください!」
おお。なんやなんや?
「可愛い制服ゲットしました!!」
はい?
「師匠!カ・ノ・ンって言うゲーム知ってはります?あれの月宮あゆの制服ゲットしました。むちゃ可愛いですよ〜」
(^^; いやー、おじさんゲームあまりやらないからわかんないあるよ。
「むっちゃ可愛いです。今度着て行きます」
着て行きますって…あんた。頼むから、着ては来ないでくれ(笑) そんなんと一緒におったら変態やと思われるやろー。
「そうですか…でも、むっちゃ可愛いんですよー。きっと師匠、感動して、うぐぅ…ってなりますよー」
うぐぅ…って、それ言葉に詰まってんのとちゃうんか?感動してんのか?
「あとねー。アンナミラーズの制服もゲットしました」
おいおい…(笑)
「師匠、今度、持っていきますから!」
持ってこやんでええ!(笑) しかし、さっきから気になってんねんけど、師匠って何やの?オレ、いつから君の師匠になったの?
「えっ。だって、師匠が僕をこの道に導いてくれたんじゃないですか」
えっ?そしたら、私はあなたの上を行く変態なんですか?(笑)
「いまでも忘れませんよー。アンナミラーズのピンクの制服は邪道や!あれはリーダー用の制服で可愛くもなんともない。アンナミラーズと言えばオレンジが基本や!って、僕のぽっぺたぴしぱし教えてくれはった…」
オレンジのほうが可愛いなぁとしか言ってないと思うんですけど…(^^;
「そして、そのあと、馬車道は土日に行かんとバイトのおねーちゃんがおらへんから気をつけろと僕を優しく諭してくれはった…。あんときの師匠は、かっこよかったなー」
それって、むちゃくちゃかっこ悪いと思うんですけど(笑)
「何も知らなかった僕に、アンナミラーズUSAは、赤い制服やったんやでとか、制服の歴史について物語る師匠のまなざし、いまでも忘れられへん…」
綺麗さっぱり忘れてください(笑) ついでに師匠ってのは今日限りにしてください。
「ブロンズパロットの道は一日にしてならずぢゃと言われたときには、目からうろこが落ちる思いでした」
目のうろこはそんなことぐらいで剥がれないようにアロンアルファでしっかりくっつけといたほうがいいですよ(笑)
「そんなわけで、師匠!ブロンズパロットの制服もゲットしたんで、一度それを着て遊びに行ってもいいでしょうか」
だから、着て来んなゆうてるやろ!!ついでに、何でもかでも制服ゲットしてくんな!道ばたに落ちてるもの何でもかでも拾って口に入れる阿呆の子やないねんから!
「ままま、そう遠慮せずに。てへへへへ、かっこ、笑い」
誰も遠慮しとらんて!しかも最後の、かっこ笑いって、そこは笑いどころとちゃうやろ!
「師匠には、いろいろお世話になったんで、アマチュア女装雑誌QUEENを進呈」
いらんて!あたしゃ何もお世話もしてへんて!あなたが勝手にひとりで変な道に突き進んだだけだってば。まるっきり冤罪やがな。もう許してくれ!
「じゃあ、学校指定のジャンパースカートと、ブラウス、ハネクトーンの3点セットで。いつ持ってあがりましょうか?」
持ってこられてもやなぁ…。とにかく遊びに来るのなら来るでいいけど、変な服、着てくんなよ!待ち合わせ場所を遠目に見て、変な服着てるようなら迷わず帰らせてもらうかんな。
「わかりました。ブラウスのサイズはどうしましょうか?通常、末尾にAとBとついている2種類がありまして、たとえば150Aと150Bがあるわけです。Aは女の子の体に合わせて作っているのでちょっとウエストからバストにかけてがきついんですよね〜。その点、Bならば、そこらへんゆったりと…」
持ってこんでもよろしい!とにかく判子持って遊びに来てくれ。いまオレに言えることはそれだけや。
「わかりました師匠。リボンは、どうしましょう?とりあえず、持って行こうと思っているのは、これは登録商標なのですがハネクトーンと言う紐状のものと、クラシックかつスタンダードな」
ガチャン!なんか、これ以上話しているとなんだか頭がおかしくなりそうだったので、電話をこちらから切ってやった(笑) というか、要件まだ伝えてないんですけど(笑) 会社作るのって、思ったより大変ですね。(何か違う苦労をしているような気がしなくもない…。こいつを有限責任社員にするのは、やめたほうがよさげだ(笑))
さてさて。
32bit×32bit/32bitの整数演算は結果32bitになるわけで、途中でビット幅を拡張しなければならないのですが、アセンブラで書けば数命令で行なえるので、このへんをなんとかしたいと思っていたのですが、::MulDivでは遅いですね(笑) さ〜さんのページで書かれてましたが、せめて、インライン展開して欲しかったです。さ〜さんのようにマクロで解決するのが、いいんでしょうね。屈服させられたままでは悔しいんで(笑)、いつものごとく匿名で感想を送ってみたりして(笑) さ〜さんのページのとりあげられてる匿名感想って、ほとんど私なんですけどね^^;
ところで、時代はDirectMusicですよ。yaneSDK2nd1.00β3のためにDirectMusicによる再生を実装してみたんですけど、こいつMCIのようにもたらないし、なかなか優れものです。
実際の演奏予定時刻から、少しずつずれていくのをドリフト(テンポ計算の誤差等によって発生する)、実際の演奏タイミングのゆらぎをジッタと呼ぶのですが、DirectMusicにおいて、前者はほぼ0のはずで、後者は、なんと2ms以下!が保証されています。すごすぎる精度です。しかも提供されるマスタークロックは、100ナノ秒の精度を持ちます。1ms=1000μs=1000000ns。マルチメディアタイマが1msの精度しか無いのと比べると、実に1万倍の精度です。メモリ上に読み込まれたファイルだって再生できてしまいます。おまけにセカンダリセグメントにおいては複数のMIDIファイルを同時再生できます。MIDIファイルを一種のSEとして使うことだって可能なわけです。再生時間取得まわりで小さなバグはいろいろあるみたいですけど、なかなかいい奴です(笑) (実装について詳しいことは、yaneSDK2nd1.00β3のソースを見てください)
第D1回 可変リージョンウィンドゥに挑戦する(社内でボロクソでした^^;) 00/11/11
いまさら、ウィンドゥリージョンの変形ですよ。ウィンドゥリージョンの変形なんか、もう何年も前にEMI CLOCKがやって、その後、半透明ウィンドゥやら何やらが出て、もういまさら、ええやん!と思っていたんですが、とりあえず、やることになったんでやりました。yaneSDK2ndのサンプル15がそれです。
年末発売のほたる荘の立ち絵を使おうと思っていたら、適当な立ち絵が余ってなくて(原画が遅れてるらしいです^^;)、しゃーないから、次回作の「蒼き大地(仮)」のキャラを自分で塗ってみました。とりあえず色彩設計だけしようと服を自分好みに変えたあと、主線の修正はせずに色決めのため2,3時間かけて塗ったのです。それがサンプル15です。もちろん、髪の毛とかは、あとで全パスで修正しなおそうと思っていたんですが、この時点で社内でボロクソの評価を受け、コテンパンにやられて気力が尽きてしまったんで、もう修正しないかも知れません(笑) そりゃ、2,3時間しかかけてないし、まだ細かい部分は適当にしか塗ってないですけど、この段階でそんな酷評されるかー? うーん。プロの世界って厳しすぎ。というか、ほとんど絵のことは初心者同然のプログラマの私にまで、プロレベルの着色を要求するか?(笑) なんちゅー会社なんじゃ。いばれたこっちゃないけど、そんなデザインセンスがあったら、こんな小学生にも劣るホームページのデザインしとらんわ!(笑) しかし、これで、やねうらおの着色生命もすっかり絶たれてしまったのでありました。(そんなの最初から無いって…) 励ましのお便りお待ちしてます。^^; というか、きっと来年、WAFFLEから販売される「蒼き大地(仮)」のグラフィックは、この何万倍も綺麗で可愛くてラブリーでテンダーでロリロリで萌え萌え〜なはずですんで、買ってやってください(笑)
ところで、ウィンドウサイズのリサイズはいかにして可能なのでしょうか?今回は、この問題について考えてみます。それというのも、旧yaneSDK掲示板に、こういう書き込みがあったからです。
なんだかなー 投稿者:ファルト 投稿日:
9月19日(火)23時37分18秒 自由にウインドウの大きさを変えられるようにできませんか。 なんか、yaneSDKで作ったものって、みんな同じようなものばかりで、やる気にもなりません。 まして作る気もなくなってしまいます。なんとかなりませんか? |
やる気にならんなら、やめとけ!(笑) 作る気もないなら、作るな!(笑) って気もします。こういう奴とかかわると、ろくなことが無いというのは、坂田君の一件で学習済みなので、掲示板ではまともに相手しませんでしたが(笑)、ウィンドゥサイズを可変させられるかというのは、一つの問題提起としては面白いです。
一応、補足ですが、ウィンドゥサイズを可変させる、というのは、ウィンドゥの4隅をドラッグして、ウィンドゥ自体のサイズをリアルタイムに変更することを意味するのだと思います。DirectXを使ったゲームで、このウィンドゥの可変が出来ないものが多いですね。これは何故でしょうか?
おそらく、普通のゲームではそういう機能を必要としないというのもあると思いますが、サイズが変更されると、DirectDrawSurfaceを作りなおすことになるので、そこで相当の処理落ちがするからというのもあるでしょう。それを描画するプログラムも、現在のクライアントの画面サイズを取得して、それに対して描画していかないといけないので、辛い意味もあるでしょう。
では、たとえば、少し大きな仮想スクリーンを作って、スクロールバーでそのなかを自由にスクロールできるようには出来るのでしょうか?これは出来そうです。ただ、仮想スクリーンをDirectDrawSurfaceとして作ってしまうと、今度は、DirectDrawSurfaceのサイズ制限に引っ掛かります。何せ、DirectDrawSurfaceは、現在の画面解像度以上のサイズのものは作れないのです。(DirectX5,6以降で少し緩和されてはいますが、いまだにこの制限を引き摺ったビデオカードが存在します/DirectX6以降で、この制限に引っ掛かるようならば、それはビデオカードのドライバ側のバグと言えると思いますが、そういうビデオカードが実在する以上、仕方ありません)
よって、フルスクリーンモードで動かすならば、その画面サイズ以上の仮想スクリーンをDirectDrawSurfaceで作るのは、よくないのです。そうなってくると、仮想スクリーンを持ちたいならば、自前でやれー!ってことになってくるわけです。
自前でやるのは、簡単です。標準コントロールを配置して、それのメッセージ応答をCWinHook派生クラスで行なえばいいんです。ちゅーか、そんなんぐらい出来ないくせに、「やる気にもなりません」って、あんた何様?って気がしなくもないです。
そういや、DirectDrawSurfaceの制限で思い出しましたが、憎っくきは、アイオーデータの糞ビデオカードです。こいつ、驚くべきことにHDCで得られる構造体が腐っているのか、Win32APIでの転送のときにMSByte(最上位バイト)が潰されるんです。具体的には、DirectDrawで32bppモードで、サーフェースからGetDCして、::SetDIBitsToDeviceすると、最上位バイトは不定になります。しかも、最上位をα値か何かの役割をさせているらしく、画面がまともに見れなくなり、当然のごとく、ヌキ色はDWORDで比較されるのでヌキ色に失敗します。CreateDIBSectionで作った32bppのDIBに対しても同じ現象が発生します。bmi.bmiHeader.biCompression = BI_RGB;を指定しているのに、最上位バイトをわざわざ不定にしますかね?::SetDIBitsToDeviceの前に、わざわざメモリ領域を0でクリアしてるのに、MSByteは不定になります。不定っちゅーか、オレがわざわざ0にしといたのに、お前、わざと潰してんねやろ!って感じですが(笑)
(株)アイ・オー・データ機器 サポートセンター 担当:桶村 お問い合わせ番号 XXXXXX の件 お世話になります。 お問い合わせ頂いております件についてですが、先のご設定において改善 が頂けない場合、現状対応策も難しい事となります。また、弊社では自作 プログラム上でのサポートは行なわせて頂いておりませんので、何卒ご了 承頂けます様お願い申し上げます。 なお、この件については開発部署に申し伝え、今後のドライバ更新で改善 可能であれば検討させて頂きたく思います。 ご期待に添えない回答となり誠に申し訳ございませんが、何卒ご理解頂け ます様お願い申し上げます。 |
なんや、自作プログラムはあかんのけ?そしたら誰のプログラムやったらええっちゅーんじゃ、桶村君!手前味噌的になるけど、yaneSDKって、やねうらおが把握してるだけでも何十社ってゲームメーカーのプログラマさんが使ってはんねんで!潜在数は、もっと多いと思うんやけど、桶村君は、それを全部敵にまわす気やな?(笑) そんなことを言われると、おじさんも、やる気にもなりません。まして作る気もなくなってしまいます。なんとかなりませんか?とか言っちゃうよ(笑)
ちゅーか、S3のリファレンスドライバでも同じ現象が起こるようで…ありゃりゃ、こりゃ望み薄やね〜。('00/11/15追加。そういや、S3は社名変更して逃げました(笑) ATIのRageシリーズでも同様の現象がWindows98SEで見れらるようで、結局のところ、IOデータは悪くなく、S3側のドライバに問題があるのか、Windows98SEのバグなのかということに落ち着きそうですね)ということは、::SetDIBitsToDeviceのあとは、最上位バイトはクリアしなさいということでんな(笑) わかったよ。クリアするよ。クリアすりゃいいんしょ(笑)
ところでついに明日、日曜に迫った東京農工大の講演会ですが、ネタはまだ考えてません(笑) やばいな〜。10人ぐらいしか来ないだろーなーと思ってたのに、結構人数いるようで…。おいおい。アットホームな雰囲気で茶菓子でも食べながらやるんと稚児単価!(なんちゅー変換しよんねん..) 教室に全員入れるんやろなぁ…。
第D2回 DirectMusicによるメモリ上のMIDI再生(にやられた^^;) 00/11/16
講演会はともかく疲れました。参加してくださったみなさん、ありがとうございました。ネタも放出しきったので、しばらくホームページに書く話題は無いのです^^;;
そういや、偶然みつけたんですが、この
おやつ絵描き歌、馬鹿すぎです(笑) 何でも、チャンピオン連載の漫画だそうですが。
ところで、WAFFLEはブランド名で、会社名は「有限会社 センキ」なのです。これは、社長の飼っていたネコの名前だそうです。ブランド名は、もっと甘ったるい名前か、美少女系だとわかる名前にしようという話もあったのですが、雑誌社とかに電話するとき「ハチミツのやねうらおです」だとか、「ほえほえロリータのやねうらおです」とかだと、とっても恥ずかしいので、「センキ」に決定したのです。ということで、うちにユーザーから電話がかかってくるとこんな感じになります。
ジリリリリ〜ン
会社のひと「はい。有限会社センキでございます」
ユーザー 「???」 ガチャン(電話が切れる)
会社のひと「なんや?いたずらか?」
ジリリリリ〜ン(またかかってくる)
会社のひと「はい。有限会社センキでございます」
ユーザー 「???」 ガチャン(電話が切れる)
会社のひと「なんや?なんや?」
ジリリリリ〜ン(またかかってくる)
会社のひと「はい。もしもし…」
ユーザー 「あのー、WAFFLEさんでしょうか?」
会社のひと「はい。そうです」
ユーザー 「あのー、盗撮マニアなんですがー」
会社のひと「はい」
ユーザー 「アンインストールできないんですけどー」
会社のひと(なんや?うちのゲーム消すんかい!?消しかたわかんねぇなら、フォルダごと消せやい!と思いながら)「コントロールパネルのですね、アプリケーションの追加と削除を開いてもらえますかー?」
ユーザー 「それはどのボタンですか?」
会社のひと「ボタンというかですね、スタートメニューからコントロールパネルを開いてですね…」
ユーザー 「スタートメニューとは?」
会社のひと「左下にスタートの文字が刻まれているのがあると思うのですが」
ユーザー 「えーと、左下にはそのようなものは無いようです。えーと、左上のスタートと書いてある奴でも良いのでしょうか?」
会社のひと(左上って、おめーが勝手にタスクバーを上にやったんだろうが!と思いながら)「ええ、それで結構でございます」
ユーザー 「それで、コントロールパネルとはどのボタンでしょうか」
(以下、延々と長電話)
まあ、こんな電話に1時間も2時間も付き合わされた日にゃ、仕事になんないので(笑)、やねうらおは、会社の電話は出ないことにしています。^^;
そんなわけで、本題ですが、今回はDirectMusicでメモリ上に読み込んだMIDIファイルを再生するときにバグるという話です。
うちの掲示板で、DEARNAさん(えらい有名な方が来てるのね〜^^;)が教えてくださったのですが、IDirectMusicLoaderがキャッシュするので、同じメモリへのポインタを渡すと、そこは実際には読み込まれずに内部的にキャッシュされていたほうを再生しやがるのです。これがまた、えらい迷惑な話であることは言うまでもありません。
気になったのでBBXの過去ログを漁ってみました。な、なんと、そこには、DEARNAさんの書き込みがありました!ちょっと引用させていただくことにします。
Subject: DirectMusicでMIDIのオンメモリー再生 Date: 2000/6/10(Sat) From: Yjさん こんばんは。 只今、DirectMusic の手直しを手伝っています。そこで、MIDI ファイルをメモリー 上に全部読み込んで再生(オンメモリー再生)を試しているのですがうまくいきませ ん。 やりたいことは、Aを再生し、停止。Bを再生し、停止というように停止後、すぐ に別のMIDIを再生することなのです。しかし、Aを停止後、Bを読み込んで再生 を行ってもBが再生されず、Aが再生してしまう(キャッシュが原因。ClearCache を 行ってみたけど意味無し)。 オンメモリー再生は果たして出来るのでしょうか? ご教授下さい。お願いします。 ------------------------------------------------------------------------------ Subject: RE:DirectMusicでMIDIのオンメモリー再生 Date: 2000/6/12(Mon) From: DEARNAさん(dearna@geocities.co.jp)(http://square.millto.net/~dearna/) 1つのバッファ領域を使って試していませんか? たとえば 1.メモリ確保 2.読込み 3.再生 4.停止・メモリ解放 5.曲の度に1から繰り返す。 私の場合には、確保・読込み・再生のメモリ領域をダブルバッファ(?)にする 事で解決しました。 1.Aメモリ確保・A曲読込み(A)・A曲再生 2.A曲停止・Bメモリ確保・B曲読込み(B)・B曲再生 3.B曲停止・Aメモリ解放・Aメモリ確保・C曲読込み(A)・C曲再生 4.C曲停止・Bメモリ解放・Bメモリ確保・D曲読込み(B)・D曲再生 5.D曲停止・Aメモリ解放・Aメモリ確保・E曲読込み(A)・E曲再生 . . な解りづらいですがこんな感じですればオンメモリで動作ました(DirectX7) 一番スマートで確実なのはIStream系を使って読込ませる方法のようです。 |
とのことです。私も早速やってみました。
// こうすれば、前回のメモリが解放される前に新しいメモリがnewされる
// ことが保証される。すなわち、前回再生したものとは違うメモリアドレスが
// 渡ることが保証される
auto_array<BYTE> cache(m_File.GetSize());
::CopyMemory((void*)cache,m_File.GetMemory(),m_File.GetSize());
m_alpMIDICache = cache;
以前紹介したauto_arrayを使った破壊的コピーのセマンティクスを使って、ダブルバッファリングを実現しています。しかし、同じアドレスへのポインタを渡した時点でキャッシュされていたほうの曲がここぞと言わんばかりに再生されます。(Windows2000/DirectX7にて確認) DEARNAさんの例では、A,B,C,D曲それぞれの曲サイズが違うので、AメモリとBメモリは毎回異なるアドレスになっていて偶然うまく動作しているのではないかとも思います。よくわかりません。すみません。
まあ、そこで別の手段を考えることにしたのです。まず、上のダブルバッファリングするのは、破棄して、キャッシュ設定を切ります。
m_lpDMLoader->EnableCache(CLSID_DirectMusicSegment, false);
これで、本当は動かなくてはおかしいんです。しかし、依然としてキャッシュされ、事態は一向に改善されません。(DirectMusicのバグです) それでは、キャッシュの明示的解放も必要なんでしょうか?
GetDirectMusic()->GetDirectMusicLoader()->ClearCache(CLSID_DirectMusicSegment);
予想通りでした。MIDIファイルをCloseするときに、上の処理を追加すると正常に動くようになりました。何と、EnableCacheでfalseにしているにも関わらず、しつこくキャッシングされていたのです!
しかし、そのあと、20回ぐらい曲を切り替えていると、鳴らなくなってしまいました。あるいはノートオンのままノートオフメッセージが送られずに次の曲が鳴ってしまいます。どうも、他にも何かキャッシュして、それがバッファオーバーしているのだろうと踏んだ私は、DirectMusicの初期化に
m_lpDMLoader->EnableCache(GUID_DirectMusicAllTypes, false);
を追加してみました。これで、すべてのキャッシュ設定が無効になったわけですが、これで結果、正常に再生されるようになりました。(しかし、キャッシュされないので再生までのタイムラグが少しあるのが気になります。やっぱりキャッシュっすよ、キャッシュ。キャッシュならポイントカードに10%還元っすよ(違))
どうも、DirectMusicはキャッシュまわりが鬼門なのかも知れません…。
第D3回 困ったときのメモリリーク対策(やってますか^^;) 00/12/01
いま、HAPPYほたる荘のマスターアップに追われてます。隣のラインで走っていたはずなのに、いつの間にやら私のラインに乗り上げてきました。いつの間に脱線したんや?というか、線路はいつの間に連結したんや? おかげさまで、プログラムも少し手伝うことになりました。他人のプログラムに手を入れるのは、非常に苦痛です。社長からは、「やねさん、ちょちょっと手を入れてなおしてよ」などと気軽に言われますが、そんな簡単なものではありません(笑) たとえて言うなれば、うんこの中に手を突っ込んで、カレーを取り出せ!(お食事中の方、すんません)と言われているようなもので、そんなところに手なんか突っ込みたくないし、構造化されていない、コメントの少ない、そしてクラス間の相互作用が大きいソースに手を入れるのは、そのうんこの中から取り出したカレーを食ってみろ!(お食事中の方、重ね重ねすみません)と言われているようなもので、非常に苦痛なのです。
苦痛の主な原因は、変数が何を担っているのかという意味論に発展するのです。たとえば、メインで使う変数はintという整数型変数でしょうけれど、ある変数xyが0のときは何を意味して、1のときは何を意味するのかを知っているのはそれを組んだプログラマだけなのです。これをboolean(真偽値 0=偽,非0=真)として解釈するのか、否定型boolean(逆真偽値 0=真 非0=偽)として解釈するのか、多値論理値として解釈するのか、小数を整数倍された値のつもりなのか、何かのインクリメンタルカウンタなのか、座標を表すのか…。仮にそれが座標を表すとして、それはスクリーン座標なのか、そのプログラマだけが頭のなかで描いた仮想画面における座標なのか。そして、後者だとしたら、それはもうそのプログラムを解釈する者にとっては到底理解しがたいものであることは言うまでもありません。その変数が実際に使用されるときになって初めて、なんとなくその意味が理解されるに過ぎません。これは本質的で非常に難しいことです。自分の知らない異国の言語があって、実際に話されている会話だけでそこで使われている単語の意味を特定せよ(ただし、文法規則については多少なりともわかっているが)というのはかなり困難な命題です。
どこまで行っても、この変数が何を意味するのか、という問題は付き纏います。そうは言っても、これを意味論や語用論の領域で解決できるとも思いません。つまり、座標を表すPOINTクラス、矩形を表すRECTクラスというように、それ専用のクラスをどんどん作っていけば駆逐される種類の問題では無いと思うのです。(そもそも作りきれません。作る必要もありません。ほとんどの場合、わざわざクラスを作らなくともintで十分です)
intで宣言された変数が何を意味するのか、それが実際に用いられるまで理解されない。そして、その変数が誤用された場合、その変数の意味の存在すら危うくしてしまう。言葉の意味作用というのは、何と儚いものなのか―――詩人ならばそう嘆くかも知れません。言葉の意味とは、実際の語用によってこそ規定される―――言語学者なら、そうのたまうかも知れません。
感傷に浸っていてもゼニなんぞビタ一文もらえないやねうらおとしては、愚痴っていても仕方ないので(というか毎度のように愚痴りながら^^;)、他人のソースを読むときは、変数ひとつ一つの意味を確定させるためgrepを何度も繰り返し、変数の意味や関数、クラスの意味を確定させるという解体作業を行ないます。そうしてみると、Knuthが文芸的プログラミングでやりたかったことが何となく理解できるような気もします。
そんなこんなで、今回は、メモリリークを発見するための方法を考えたいと思います。確かBCBとかではメモリリークを発見するための機構自体がそなわっていたと思うのですが(使ったこと無いので詳しくは知りません^^;)、VC++ではそういう機構は無いので平気でメモリリークします。メモリリークってのは、確保したメモリを解放していないなどにより資源が漏れることです。ここでは、newで確保したメモリがdeleteで解放されていないという状況に限定しましょう。
少なくとも、商品として流通させるには、最低限度のモラルとして、このメモリリークを無くしておかなければなりません。そうは言っても、ネスケでさえもメモリリークしていたような記憶があります。タスクマネージャで見ていたら、どんどん使用メモリ量が増えていきます。まあ、メモリリークしていたからと言って、即メモリエラーで落ちるわけではないので、実害はそれほど無いのかも知れません。
とりあえず、C++で書いているのならば、operator newとdeleteをオーバーロードしてログを吐かせると良いかも知れません。
void* operator new (size_t t){ void *p = ::malloc(t); FILE*fp=fopen("debug_memory.txt","aw+"); fprintf(fp,"new %.8x :%d\n",p,t); fclose(fp); return p; } void operator delete(void*p){ if (p==NULL) return ; FILE*fp=fopen("debug_memory.txt","aw+"); fprintf(fp,"del %.8x\n",p); fclose(fp); ::free(p); } |
これで、ログは吐けました。確保して解放していないメモリエリアを探すために、このファイルを検証するプログラムを書きましょう。
# memcheck.pl while (<>) { if (m/new[\s]*([a-f\d]+)[\s]*:[\s]*([\d]+)/){ $dat{$1} = $2; } elsif (m/del[\s]*([a-f\d]+)/){ delete $dat{$1}; } } foreach (keys %dat) { print "$_ : $dat{$_}\n"; } |
サクっとPerlで書いてみました。上記のプログラムは、標準入力からテキストを得て、標準入力に出力しているので、実行は、
perl memcheck.pl < debug_memory.txt > result.txt
のようにして行ないます。 そして、newしてdeleteされていないメモリ/サイズが特定できれば、 今度は、以下のoperator newの関数内で、そのメモリであるかを チェックするif文を書いて、その条件成立時のところにブレーク ポイントを仕掛けて走らせてみます。そうすれば、どこで確保された メモリが解放されていなかったかがわかります。 (下例)
void* operator new (size_t t){ void *p = ::malloc(t); FILE*fp=fopen("debug_memory.txt","aw+"); fprintf(fp,"new %.8x :%d\n",p,t); fclose(fp); if (p==(void*)0x12345678){ *(BYTE*)p = 0; // ここにブレークポイントを仕掛ける } return p; } |
どうも、Windows2000でブレークポイントを下手に置くと、そのままハングしてしまうのですが^^; 非常に不便です。仕方ないので、この手のトレースは先日買ってきたFiva(Windows98SE)でやってます。DirectXをHALでやってるといかんのか??うーむ…。
そして、これを使ってyaneSDK2ndを調べていると、メモリリークが数箇所見つかりました^^; auto_ptrExにoperator = で new で確保されたメモリをそのまま代入している奴。これは、所有権は移動しないから、これでは自動的には解放されないのです。いま考えると、自動的に解放する必要がないメモリならば、auto_ptrExで管理する必要はないわけだし、operator = で代入したときも所有権は持っていても良かったのかなぁ…と少し思わなくもないですが^^;
あと、その他の部分は、newはほとんど使っていないし、メモリリークはしていないと思っていたところ、何とCSpriteCharaからメモリリークが検出される。auto_ptrExとか、そっちのほうがバグってるのかな?と思ってくまなく調べるがバグらしき箇所が見当たらない。ということはnewしたものをdeleteしていないタイプのメモリリークではないし、スタックのようなものを際限なく積んで重層的に蓄積していくメモリリーク(正確にはリークしているのではないが)でも無い。そうです。もうわかりましたね。あれですよ、あれ。
インターフェースクラス(ベースクラス)にvirtualなデストラクタをplace holderとして用意していなかったのです。
あいたたた〜。おじさん、あんましインターフェースクラス書かないから、そんなことすっかり忘れてたあるよ。よって、派生クラスのデストラクタが呼ばれずいつまで経っても解放されないと…いう事態になっていたのです。こんなチョンボをしないためにも、C++の言語セットのなかにIDL(interface definition language)のようにinterfaceを記述するための構文が欲しかったですね。(などと責任転嫁する奴^^;)
第D4回 MatroxのG200/G400の不都合について【前編】(買ってきたがな^^;) 00/12/24
まず、前回のデバッグ手法は、かなり汎用性があるものの、VC++ならば、_CrtSetDbgFlagを使えばメモリリークの検出が出来るらしい。(ザキンコさんからの報告) あうー。私がやってたのは、なんやったんや…(笑)
そういや、この鈴希 零さんからクリスマスカードが来てたので、この場をお借りして紹介します。あんがとね>鈴希さん
私に至っては、クリスマスも正月も返上で仕事してます。いまの仕事がひと段落しない限り、私に正月は来ないでしょう。(正月は来年の7月ごろかいな…^^;)
それはさておき、12月22日はNavi(パソコンゲームのショップ)が深夜0時から開店してたので、ご存知のかたもおられるでしょうが、「顔のない月」・「Natural 0」・「夜勤病棟」・「大人の缶詰」の発売日なのです。ついでに、でじこ画集「CHOCOLA2000」も発売になり、日本橋は異様な熱気に包まれていたのでした。
だからというわけでもないのですが、昨日、某社のプログラマと日本橋に遊びに行ったときにMatroxのG200/G400の不都合、あれ、なんとかならんか〜?というのが話題にあがりました。(彼のレポートは、これです)
私のyaneSDKでもこのバグに悩まされています。yaneSDKを使った最近の作品では【HAPPYほたる荘】でも、G200/G400ユーザーから動かないという報告がありました。(ハードウェアアクセラレータのレベルを下げれば動く)
言い訳するわけではないのですが、「顔のない月」でも、G200/G400でこの不都合が出ます。もっとも、この「顔のない月」のROOTは、新規ブランドでしかもプログラマを募集しているぐらいなので、プログラマにはあまり力が無いのかも知れませんが、これだけビッグタイトルでも不都合が出るということからも、G200/G400がいかに腐っているか想像だに難くはありません。
このビデオカードにはかなり致命的なバグがいろいろあります。Matroxには、Millennium(商品名)の時代からドライバはバグだらけなので有名ですが、G200/G400に至ってはボロボロのようです。
とは言っても手元にないと調べ様もないので、実際に購入しようとKa-Syaさんと日本橋を徘徊したのでした。G400はあちらこちらで売られているんですが15,000円前後が相場で、どうせバグの確認だけならG200でええやろと思っていたのですが、ソフマップ・スタンバイ・DOSパラ・TWO TOP等には在庫なし。徘徊2時間後、小さなパーツショップで7,000円弱で売られているのを発見。「これについてるドライバでちゃんとバグりますかね。バグんなかったら買う意味全くないんですけど。バグんなかったら返品してもいいですかね」などとわけのわからんことを店員に確認し(嫌がらせとも言う)自腹を切って購入したのでした。詳しい調査報告は、次回に書きます。
ところで、DirectDrawSurfaceは現在の画面モードと同じ(bpp/pixel format)ものしか作れないと思っていたのですが、そうではないのです。ビデオメモリではなくシステムメモリ上にサーフェースを作るならば、現在の画面モードと違うサーフェースが作れるのです。あとは、サーフェースからDCを取得して、::BitBltで転送してしまえばOKです。こうすれば、DirectDrawSurfaceが一種のDIBSectionのように使えるというわけです。まあ、思ったほど遅くもないようです。とりあえずG200/G400で動かすだけならば、そういうのもありなのかも知れません。
第D5回 倒れるまでヤキを食え!(いいかげんプログラムってうっとおしいねん) 01/01/17
やねうらおは大阪に舞い戻ってきて有限会社を興すに至ったのですが、東京のほうにも時々仕事で出かけます。大阪名物と言えばたこ焼きなのでお土産には必ずたこ焼きを持って行くように心がけています。大阪人は、朝昼晩3食たこ焼きを食べ、おやつにはお好み焼きを食べていると思われがちですが(本当に思われてるんだってば)それは偏見であります。人はたこ焼きのみに生くるにあらず。あっしかし、うちの家って、ご飯のおかずがお好み焼きで、そのあとのデザート(?)がたこ焼きだったことはあるなぁ…。そんなにデンプン質ばっか摂ってどうすんねや…。
ときに、お土産にたこ焼きを持っていきたいのですが、大阪の自宅から東京の打ち合わせ場所まで約6時間かかるため、すっかり冷めてしまうのです。仕方ないので、前回はたこ焼きアラレを持って行くことにしました。向こうに着いてから先方様とご一緒にたこ焼きアラレを食べさせていただいたのですが、何とタコが入っていないのです!タコの入ってないタコ焼き…
映画『ミナミの金融伝』でチンピラが屋台のおっさんに絡んで「お前とこのタコ焼き10個のなかに7個しかタコ入ってなかったがな。タコ焼きにタコ入ってなかったら、それはたこ焼きやのおて、単なる“焼き”やがな」と言っていたのを思い出し、これはたこ焼きアラレではなく“焼き”アラレという結論に至ったのでした。ちなみに、この映画ではこのあとこのチンピラに反撃して「おっさん何か。ほんならタイヤキにタイ入ってるんでっか?人形焼きに人形入ってるんでっか?ベビーカステラに赤ん坊入ってるんでっか?」と言う。ベビーカステラに赤ん坊入ってたら怖すぎだしひよこ饅頭にひよこは入れない。言うまでもなく、ハト麦茶に鳩は入ってないし、ハトサブレは鳩をサブってあるわけではない。(サブるってなんや?) もちろん、広島焼きに広島は入ってない。(どうやって入れるんじゃい!>広島)
“焼き”の話はどうでもいいんですが、その後、さ〜さんを電話で呼び出し、白木屋へ直行。「大阪には食い倒れってあるでしょ。大阪人は倒れるまで食うんですわ。今日はおごりますんで、とりあえず、ここ載ってるメニュー、1品ずつ全部頼みましょか」とやねうらおの馬鹿発言を受けて、馬鹿食いを始める二人。戦いの火蓋は切って落とされたのでありました。
話題の中心は、『バルドバレット』の爆風、半透明できれーやなーの話。一般に、この手の爆発ってのは、エネルギーの拡散であるわけで、途中で建物等を破壊するので、その分、エネルギーは減衰すると。仮にエネルギーの減衰がしないとしたら、エネルギーの拡散具合は、どうなるのでしょうか?3次元的に広がるならば、球体の表面積は4πr2すなわち、半径の2乗に反比例して弱くなりながら拡散すると言えるでしょう。しかし、実際には、地上付近では平面とみなせるので、円周は2πrなわけで、半径に反比例すると。あとは、途中で、破壊と伝播上のロス分だけエネルギー損失しながら、弱くなるわけです。ちゅーか、そんな計算、ゲームごときで真面目にやることないやろ!とか言いながら食うこと食らうこと3時間。わかったことは、酒を飲んで倒れるのは簡単かも知れないが、食いまくって倒れるのは難しいという幼稚園児もびっくり!するようなしないような結論だけであった。
しかし、いまや、半透明の使っていないゲームはふた世代昔のゲームとなりつつあります。そして、半透明や回転と言えば、2次元であってもDirect3Dを使うのが常識となっています。Windowsでまで3Dをやりたくないやねうらおは、これを回避し、自前で半透明やら回転やらaffine変換やらの転送ルーチンを書いていますが、これはかなり馬鹿な行為なのかも知れません。しかも、グラフィックの描画系のルーチンは、等倍・拡大縮小・ミラー・affine変換等々、そいつらに対して、通常転送・色抜き転送・Add Color(飽和加算)・Sub Color(飽和減算)・半透明等々があって、それらの組み合わせ、しかも私の場合、Pentium用、MMX用、PentiumII用に最適化された転送ルーチンを用意しないと気が済まないほうなので、ルーチンの数は指数的に増大します。非常に開発効率が悪いです。なんとかならんもんでしょうか?
そう言えば池内さんのホームページで、GTL(GraphicTemplateLibrary)というのが紹介されています。
GTLの基本思想は、STLに根ざすものがあって、functor(過去ログ、第C7回を参照のこと)とtemplateで汎用的なグラフィックライブラリを目指されているようです。templateを使っているので関数の直交性には素晴らしいものがありますし、functorがinlineで展開されれば速度もそれなりに速いです。ソースも綺麗で短いです。
私は、こういうアプローチはやってこなかったのですが、今後、マシンスペックが速くなってくれば、私のようなやりかたはナンセンスになってくるのかも知れません。(というか、すでにナンセンスだという気がしなくもない)
第D6回 MatroxのG200/G400の不都合について【後編】(金返して欲しいねん) 01/01/29
最近、炬燵で寝てることが多いです。炬燵に入って仕事してると、そのまま眠くなって眠っています。起きると、キーボードの上で眠ってたようで、画面には意味不明の文字列が…。ひょっとして、俺が寝てるあいだに小人さんが時計から出てきて仕事してくれてたんやろか?それにしては、なんたるおそまつな仕事なんや。何を書いてあるのかさっぱりわからんで。小人さんは、相当、頭が悪いようだ…
などと、寝ぼけていると電話がかかってきた。
「師匠!!おはようございます」
なんやなんや?この聞き覚えあるような、もう覚えていたくないような声は…あっ。変態女装マニアやんけ。
「誰が変態女装マニアですか!!誰が!それに、女装とは失敬な!いくら師匠でも、今度だけは許しませんよ。私のはれっきとしたコスプレです。そんなことを言うと、全国1億4千万人のコスプレファンが黙っちゃいませんて」
全国1億4千万人のコスプレファンて…それ日本の人口全部やがな!
「コスプレのこと専門用語で言えば、コスって言うんですけど」
短くしただけやん!?
「アンミラとか、神戸屋とかは、コスですよ」
まあ、あれはコスプレとも言えなくないわな…。そしたら、女子高生とか、あれもコスプレ?
「女子高生のコスプレですがな」
んー。そしたら、自分、こないだ某会場で、ふりふりひらひらなピンクハウス系の服着てへんかったけ?あれもコスプレ?
「コスプレです」
それは一体、何のコスプレなん?誰かのコスチュームなん?
「誰って、あなた、そりゃオリキャラですよ」
オリキャラって何やねん!?お前のオリジナルなんけ?
「さいです!」
さいですって…。オリキャラのコスプレ…オリキャラのコスプレ…。それ女装とどう違うんじゃい!!
「師匠、わかってない。師匠、ぜんぜんわかってない」
わかってないというより、そんなのわかりたくないねんけどな…。
「あのねー、正直僕は師匠に失望しました」
なにがやねん?
「オリキャラなんですよ。オリキャラ。あのキャラクターをデザインするのに、僕は半年かかってるんですよ。名前は、麻里って言うんですよ。アンドロイドなんですよ、アンドロイド!」
繰り返さんでええ!
「師匠には、麻里の下の名前も特別教えてあげましょうか」
ちょっと待て…。下の名前って、麻里が下の名前と違うんかい!?
「ふっ。そんな馬鹿な…」
そんな馬鹿なって…(笑) 麻里やったら、普通、下の名前と思うやろ!麻里が上の名前やとしたら、下の名前はなんやねん?麻里の下について違和感ない名前なんてあるんかいな?
「麻里の下の名前は、ちゃそですよ。フルネームで言えば麻里 ちゃそ」
……。まあ、敢えて突っ込まへんけどな。半年かけて考えて、それか、みたいな落胆はあるけどな。
「その麻里ちゃそが、ある日、人間の心を持つんですよ。なぜなら、麻里ちゃそは、いつも継母にいじめられていたからです」
ちょっと待て。継母って何やねん?継母もアンドロイドなんか?
「当たり前じゃないですか!娘がアンドロイドで継母が、スーパーサイヤ人だったらそりゃビックリ!でしょう?」
いや、確かにビックリけど、スーパーサイヤ人が継母というとこがビックリなだけで、継母がアンドロイドでないことにビックリしてんのちゃうわ!
「ともかく、なんと、その麻里ちゃそは、継母から虐待を受けるんですよ。熱湯とかかけられて、ほっぺたとかぴしぱし叩かれて蜂に刺されたみたいになるまで腫れ上がってるんですよ。それでも麻里ちゃそは、人前では決して涙を見せないわけですよ。麻里ちゃそは、ある日、こう思うんです。だけど涙が出ちゃう…だって女の子だもん、はーとまーく」
何がはーとまーくやねんな(笑)
「そうです。麻里ちゃそは、本当は、毎晩、枕を涙で濡らしていたわけです。人前では強がっていただけなんです。わかりやすく言えば、蜂っつらに泣き!ですよ」
そんなん、なんもわかりやすないわ(笑) 蜂っつらに泣き!?なんですか、それ。
「それを見かねたわたくしが、麻里ちゃそに変装して、麻里ちゃその代わりに継母にいじめられるという、そういう自己犠牲的な愛情の上に成り立つコスプレなんですよ」
言ってることがさっぱりわからんぞ!たとえて言うなれば、ミニモニのジャンケンぴょん!の歌詞のなかで突然、「緑の野菜を食べるだぴょん!」ゆうてる部分ぐらいわけわからんわ!結局、女装とどうちがうんや?
「師匠!いままで何を聞いてたんですか!」
えっー?俺わるいんか?
「私はね、女装とか言われるとすっごい腹が立つんですよ。まるで私が、女性ものの下着とかブラジャーとか付けて水玉のワンピとか着て、カツラかぶって外出歩くのが大好きな、神田の『エリザベス』とか名古屋の『レディス三島』とか大阪の『アダム』とか『ばら』とかに出入りしている変態野郎みたいじゃないですか」
そんな女装専門店の店の名前がすらすら言える時点で、弁解の余地無しだと思うんですけど…。それに前回、アマチュア女装雑誌QUEENを進呈とか言うとったやん?
「失敬な!いい加減、怒りますよ!だいたい、コスプレにしても好きでやってるんじゃないんですからね!」
えっ?違うの?
「あたり前じゃないですか!なんで、僕がそんな変な服着て、出歩かなあかんのですか。僕がコスプレするには、それなりの理由があるんですよ」
ほほー。その理由とやらを、聞かせてもらおか。
「師匠は知らんでしょうが、かーどちゃぷたーさくらってアニメがあるんですよ」
カードチャプターでなくて、カードキャプターだと思うけど。
「そうですよ。そのキャプチャーですよ」
言えてへんやん(笑)
「僕ねー、やっと、そのクロウカード編がコンプリしたんですよ」
それ何の話?DVD?
「阿呆言いなさい!誰がDVDなんてチンケな円盤集めて喜びますか!」
チンケな円盤とはまた身も蓋も無いやっちゃな…。そしたら、一体、何がコンプリしたわけ?
「師匠、知らんと思うから教えてあげますけどね、このチャプターはね、しょっちゅう、コスチュームが変わるんですよ。クロウカード編だけでも、学校の制服とスクール水着とチアリーディング部の制服を除いても…、ひい、ふう、…、みぃ、よぉ、…、いつ、むー、…」
ちょっと待て。ひょっとして、そのコスチューム、全部集めたんか?
「集めたとはなんたる侮辱!作ったんですよ!作った!売ってたらこんな苦労しませんがな。金さえ出せば出来るプチブルジョアチックなお手軽極楽インスタントコスプレッソな連中とは所詮、立ってる土俵がカニミソとタケヤミソほど違うんですよ!こっちは清水の舞台から飛び降りる覚悟で毎晩、ミシンでゴトゴトじっくり煮込んで作ったんですよ」
言っている意味がさっぱりわからんが、とりあえず清水の舞台から飛び降りるって日本語の用法違うような気が…
「僕がどんな想いでミシンでゴトゴトやったか、師匠にはわからへんでしょう?」
わからん。どんな想いなん?
「そりゃ、大道寺知世ちゃんになったつもりで、木之本桜ちゃんに着させてその活躍ぶりをビデオに収めるために…」
なりきってるんかい!
「ともかく、そうこうして毎晩、ブイブイとミシンを走らせて、そやなー、だいぶ走ったなー。網走刑務所から湘南の海ぐらいまでは走ったかなー。それで、半年がかりでやっとコンプリしたんですよ。ところが、完成したときに、とても重大な事実に、ふと気づいたんですよ」
ふむふむ…。
「あっ。これ着てくれる木之本桜ちゃんなんて、現実には、実在せーへん。どこにもおらへんて…」
最初に気づけ!
「だからね、これ着てクロウカードを封印できんのは、やっぱり自分しかないと。そう思ったんですよ。だから、これ着て活躍しよかなーなんて思うに至ったわけですよ。クロウカード編だけに苦労しまっさ〜。わはははは〜」
ガチャン! これ以上、話していると頭がおかしくなりそうだったので電話を切ってやった。こいつは一体、何のために電話をしてきたのだろう?いまをもって謎である。たぶん、永遠に謎のままであろう。とりあえず、彼の電話は番号拒否しておこう。やねうらおに出来ることはそれぐらいしかなかったのである。
前置きが長くなってしまいました。えっーと、前前回の続きなんですが、やられました。MatorxのG200ですが、Windows2000では標準でドライバを自動認識して、そのドライバではバグりません。これでは何のために買ったか、てんでわかりません。唯一のメリットは、HighColorに15ビットモードと16ビットモードがあるので、両方のプログラムのテストが出来ることぐらいですか(笑) とりあえず失敗の巻です。マフティさんからレポートが届いていますので、転載します。
いつも「スーパープログラマーへの道」を楽しく読ませて頂いています。 第D4回のG400についてですが、 最近のドライバーでは起きないようです。(Win98notSEにて確認) バグフィックスされた様です。 他に似たような症状が起きるビデオカードとしてSavage4があります。 S3のチップなので第D1回と絡むかも知れませんが。 症状としては、32bppでサーフェイスにビデオメモリを使用したときに、 抜き色転送が出来なくなるという物です。(ソースは下を参照) 実際には指定した点だけは抜かれているのですが、 そのほかの点はそのまま転送されてしまいます。 しかもやっかいなのが、等倍転送の時だけこの現象が起きるという点です。 1ドットでも拡大or縮小すれば問題なく抜き色転送されます。 16bppではこの現象は起きないので、 32bppの等倍転送のルーチンに問題があるようです 他のビデオカード(TNT・G400・i815等)では動きますから プログラム自体に問題は無いはずです。 解決方法としては、システムメモリに全てのサーフェイスを作ることです。 システムメモリに作るとHELになるらしく(経験則です。確認はしてません) ハードウェアアクセラレータのレベルを下げるのと同じ事になります。 この方法は、他のビデオカードの他のバグ(<日本語がおかしい(笑)) にも有効です。 詳しくは書きませんが、i810でGetDCがこけてたときもこれで直りました。 以降自分のプログラムにはビデオメモリに作るかシステムにするかを ユーザーが指定できるようにしています。 これならば、ハードウェアアクセラレータを替えたときの再起動をしなくてすみ ますし、バグはビデオカードメーカーのせいに出来ます(笑) なかなかうまい方法だと自分では思っています(笑) 以上の確認はWin98、DirectX7.0aで行いました。 |
マフティさん、ありがとうございます。私が調べようと思っていたことと、言いたいことはすべて代弁していただけたようで助かりました(笑) 時間が来てしまったんで、今回はこのへんで。
第D7回 new / deleteは遅いのか?(遅いらしい…) 01/02/19
仕事の合間を縫って、自作のオブジェクト指向スクリプトの構想を練っていたのですが、やはりポインタはどうしてもスマートポインタでなければ嫌なのです。メモリリーク/メモリバイオレーションは一切発生しないことを大前提としたいのです。
スマートポインタの実装方法として、参照回数を保持する方法と、双方向リストを保持する方法とあると思うのですが(『天才ゲームプログラマ養成ギプス 第11章』を参照のこと)、前者のほうがコピー時のコストが小さいため、やはり前者にしようかと思ったのです。このとき問題となってくるのが、newが一回多いということです。
newをどう実装するかは、C++の実装系、あるいはOSレベルの問題なのかも知れません。しかし、常識的に考えても、確保〜解放されたメモリをまた再利用したりするのは、とても大変な仕事のはずです。deleteではアドレスしか渡されませんから、少なくともそのサイズを記憶しておかなければなりません。
ということで、事前に配列を確保して、そこから切り出して渡してやるようなnewを作ったらどうかと思ったのです。(STLのvectorとかも、それに近いコンセプトに根ざしていますね) スマートポインタとかで必要となってくるのは、たいてい16バイト以下の小さなメモリで、このような小さなメモリを高速にnew / deleteするために、次のようなプログラムを書きました。
// 一斉に確保するメモリ static BYTE* g_lpaMemory = 0; // 一斉確保するメモリ static bool g_bInitialize = false; // 上のメモリは初期化されているか? const int g_nBlockSize = 16; // 1ブロックのサイズ(byte) const int g_nBlockNum = 1024; // この数だけ一気にブロック確保 static int g_anBlank[g_nBlockNum]; // 空きエリアを記憶する static int g_nMax; void* operator new (size_t t){ static BYTE aanHeap[g_nBlockNum][g_nBlockSize]; // ヒープメモリ // ローカルで確保するのは、終了時に自動的に解放されるように if (!g_bInitialize) { g_nMax = g_nBlockNum; // すべて空き for(int i=0;i<g_nBlockNum;++i) { g_anBlank[i] = i; } g_lpaMemory = (BYTE*)aanHeap; // これをグローバル変数に伝播させておく g_bInitialize = true; // 初期化終了 } if (g_nMax == 0 || g_nBlockSize < t) { // 空きエリアあらへん or 規定サイズ以上のメモリ確保要求 return (void *)::malloc(t); } // 空きブロックを返す return (void*)&aanHeap[g_anBlank[--g_nMax]]; } void operator delete(void*p){ // newが一度も呼び出されていないのにdeleteが呼び出されることは無い // (と仮定している) int n = (BYTE*)p - (BYTE*)g_lpaMemory; // 自分のヒープ上のメモリか? if ((0 <= n) && (n < g_nBlockNum*g_nBlockSize)) { n /= g_nBlockSize; g_anBlank[g_nMax++] = n; // メモリの解放 } else { ::free(p); } } |
たいしたことはやって無いのですが、グローバルで宣言されているクラスのコンストラクタでnewしている部分でもうまく動作しなくてはならず、かつ、グローバルで宣言されているクラスのデストラクタでもdeleteが動作しなくてはならないので、staticなローカル変数を使っています。(グローバル変数がstaticになっているのは、変数をファイルスコープに限定するためです) もっといい方法があるのかも知れませんが、私にはこれぐらいしか思いつきませんでした。
さて、気になる速度のほうですが、簡単なテストをしてみたところ、小さなメモリの確保〜解放ならば、4倍以上の差がありました。こまめにnew〜deleteを繰り返すタイプのプログラムでは、この差は結構大きいです。