Decremented Blog

プログラミング、釣り、工作など、ジャンルを特に定めず、適当に書かれたブログです。

[C++] C++における printf の恐怖

2009-01-29 00:24:26 | プログラミング・開発
前回の記事で、CStringを利用しないことで高速化する内容を書きました。
実装としては、CStringを自作の文字列クラスに置き換えた訳ですが、ちょっと困ったことになりました。

具体的には、sprintfやTRACE使用時、文字列クラスを文字列型にキャストして渡さないと、正常に出力できなくなりました。
具体的には、以下のような感じです
(CMyString は文字列のラッパクラスで、char *への変換オペレータを持つとします)
// 文字列クラス
CMyString str ="Test";

// 動かない
printf("%s", str);

// 動く
printf("%s", (char *)str);

理由としては単純で、可変長引数が型情報を持たないため、正常に型変換オペレータが呼ばれないためです。
つまり、printfには、strの内容がそのまま渡されます。
(構造体の値渡しと同じで、メンバ変数のメモリ領域がそのままコピーされます)

大抵、文字列クラスにはメンバ変数として文字列ポインタ以外に長さなどの情報を持っており、それらもコピーされてしまうため、正常に動作しません。
MFC/ATLのCStringでは、メンバ変数を文字列へのポインタだけにすることで解決してるようです。
メンバ変数がポインタのみのため、sizeof(CString)を見てみると4となっていることが分かります。

自作の文字列クラスもそのようにすれば良いのですが、用途の都合上、メンバとして固定長の文字列バッファを持っているため、不可能です。
いっそ、C++用のsprintfを作ってしまえばよいのですが、さすがに面倒です。
C++なら、可変長引数を使わなくても、暗黙変換と既定の引数をを使えば、実行時にちゃんと型チェックする安全なsprintfが実現できそうですが・・・


[C++] MFC/ATL の CString は、早い?遅い?

2009-01-28 00:55:38 | プログラミング・開発
前の記事の続きで、CWorkspaceと言うツリー型データ管理クラスの話です。

前の記事でXMLへの出力を高速化した所、データのコピーより、一旦XMLに変換して再ロードしたほうが10倍以上早いと言う謎の状態になりました(笑
データコピーは、ツリーを再帰的にコピーして行くよう実装されており、特段遅くなる理由も見つかりません。

よくよく調べてみると、XMLの時と同じくCString原因のようです。
再帰的にコピーする時、再起呼出しごとに新たにCStringを確保しているのが問題のようでした。
CStringは、MFCとATLで使用できる文字列クラスですが、こいつの確保に時間を食っているようです。
ちゃんとコードを読んでみないと分かりませんが、CStringは毎回newでメモリを確保しているわけではなさそうです。
高速化のためにメモリプールを使ってるのでしょうか?

それでも速度の低下が発生してしまうので、私の使い方は、CStringの設計者の想定外のようです。
CStringを使用しないようコードを書き直したところ、十分なパフォーマンスが出るようになりました。
CStringの確保は、毎回new等でメモリを確保するよりも高速なようですが、あまりに大量に確保するような使い方はよした方がよさそうです。

※追記
CStringのメモリマネージャは自分で実装することもできるようです。
また、CStringがコピーされる場合、変更が加えられるまで前の文字列と同じメモリ領域を共有し、確保とコピーはされないようです。


[XML] 自作パーサが遅いので、Expatを使ってみる

2009-01-25 23:44:53 | プログラミング・開発
私の作るソフトは、内部でCWorkspaceと言う名前のツリー型データ管理クラスを使用しています。
設定の読み書きや、内部データの保存に利用するのですが、04WebServerでは、リクエストに関するほぼ全ての情報がこのクラスで管理されます。

04WebServerでは、処理中のリクエストの情報をリアルタイムに表示する機能がありますが、これはサーバの内部情報をサーバコントローラに送信することで実現されています。
この時、CWorkspaceで管理されている情報を一旦XMLへシリアライズし、テキストデータとしてサーバからコントローラへ転送されます。
(1xxは、XMLではなくて、独自形式にシリアライズしています)

これまでXMLの読み込みは自作のパーサを使用していたのですが、適当に作ったため、とっても遅いことが分かりました。
具体的には、100接続分の情報、約210KのXMLを生成するのに約3秒、読み込むのに約4秒かかります(共にCore 2 Duo 1.6GHz)。
正直、使い物になりません。

まず、XML生成側を最適化しました。
どうも、CStringの生成とつなぎ合わせ(+演算子)が非常に遅いようなので、利用が最低限になるように変更しました。
その結果、約70msまで高速化しました、42倍速ですね(笑

次に、読み込み側なのですが、XMLパーサの高速なものを自作するのは正直労力の無駄ですので、ライブラリを使うことにしました。
1xxでも利用していたMSXMLは遅いことが分かっているので、同じマイクロソフト製で高速なxmlliteでも良いかと思ったのですが、Windows XPでは標準で入っていないようです。
C++なのでXerces-C++でも良いかと思ったのですが、ちょっと規模が大きすぎるので、Expatを使うことにしました。

文字コード周りのラッパを作り、組み込んでみます。
・・・早いですね、このパーサ。
約100ms程度まで高速化しました。
コードの最適化が切ってあるので、最適化を有効にするとさらに速度の向上が期待できます。

XMLの読み書きは、これで十分な速度が確保できました。


[マルチスレッド] 単純だが気付き難いミス

2009-01-20 22:26:10 | プログラミング・開発
04WebServer 2を開発していて、稀に発生する謎のバグに遭遇しました。
マルチスレッドに起因するバグで、原因が分かってみれば単純なのですが、なかなか悩みました。

新規接続ごとに内部でIDを振るのですが、リソース管理にバグがあり、タイミングによって同じIDが複数の接続に振られてしまうことが原因でした。
コードは以下のような感じです。
	contextName.Format("Request_%08x", m_contextID);
	m_contextID.IncCounter();
m_contextID.IncCounter()は、排他処理を行うインクリメントです。
このコードだと、タイミングによっては同じ値のcontextNameが生成される可能性があります。
contextName.Format()終了後に、コンテキストスイッチが発生する可能性があるためです。

以下のように修正しました。
IncCounter()は、インクリメント後の値を返しますので、これで問題ありません。
	contextName.Format("Request_%08x", m_contextID.IncCounter());
マルチスレッドのコードは、気をつけないと単純なミスでハマりますね。
変な動作をすると思ったら、まさか、こんな単純なミスをしていたとは・・・



岡山特産 「あたご梨」

2009-01-08 22:25:15 | Weblog
岡山特産の「あたご梨」をもらいました。
とりあえず、いつもの魚と同じく恵比寿ビールと比較してみます。


デカイ・・・w


ちょっと大きく切りすぎました。
一個で普通の梨半分と同じくらいありそうです。

なかなか良い味でした。味は甘めです。
ちょっと柔らかめで、洋梨と普通の梨の中間くらいの硬さです。
あたご梨って、高いけど美味いんですよね。
関東じゃあまり見ませんけど、何でだろう?


[04WebServer] 2.0 開発状況(2009/01/08)

2009-01-08 02:49:51 | プログラミング・開発
04WebServer 2.0の開発状況です。

しばらく内部構造を変更していたのですが、ようやく完了しました。
内部構造の変更は、外部モジュールの開発を楽にするためです。
ちょっと迷走したこともあり、2週間もかかってしまいました。

これで、サーバ本体の方は完成が見えてきました。
本体が完成したら、次はUIですね。

----

本日、Unleakの新バージョンをリリースしました。
まあ、新バージョンと言っても、数行修正しただけなのですが・・・


[つこうた] IPA職員がファイル交換ソフトでウイルスに感染、写真など流出

2009-01-04 23:39:48 | Weblog
どうすんの? これ?

36 名前: すずめちゃん(東京都)[] 投稿日:2009/01/04(日) 20:47:02.40 ID:clKXoxNt
Re: お送りいただいた情報について[ipa-info 17101]

IPA-PR To 自分、 IPA 問合せ - 一般
詳細を表示 20:40 (30分前) 返信

フリーザ様

IPA(情報処理推進機構)広報グループです。

お送りいただきました情報については現在調査を行っております。
当機構は、情報セキュリティ対策を推進しており、ファイル交換ソフト(Winnyな
ど)の利用の危険性についてもかねてから注意喚起を行ってきたところです。今般こ
のような事態が発生したことについて、陳謝申し上げるとともに、再発の防止に全力
を尽くしてまいります。

情報提供いただき誠にありがとうございました。

> -----------------------------------------------------
>
> ご意見・お問い合わせ内容:
>
>ほっほっほっほ
>やってくれましたね…
>まさかあなた方が使われるとは驚きでしたよ… 
>http://tsushima.2ch.net/test/read.cgi/news/1231004691/
> ------------------------------------------------------

IPA職員がファイル交換ソフトでウイルスに感染、写真など流出
http://internet.watch.impress.co.jp/cda/news/2009/01/04/21994.html

最近行ったネットカフェのPC、WinMXが入れてあってワロタ
起動の度に初期化されるみたいだから、最初から入れてるのね

[C++] 多重継承の使い所

2009-01-03 20:26:29 | プログラミング・開発
04WebServer 2.0を開発していて、C++の多重継承でちょっと困った問題に遭遇しました。
04Webserver 2.0では、フィルタを用いてサーバの動作を変更できます。
フィルタはC++のクラスとして宣言するのですが、楽をしようとして多重継承を使ったら、上手く行きませんでした。

フィルタのインタフェースの定義は、だいたい下記のようになっています。
//! ベースインタフェース
class IModule
{
public:
    //!	削除
    virtual void DeleteModule() = 0;
};

//! フィルタインタフェース
class IFilter : public IModule
{
    // ソース参照
};

//! フィルタ生成インタフェース
class IFilterFactory : public IModule
{
    //! フィルタ取得
    virtual IFilter *GetFilter() = 0;
};

IModuleは、共通のラッパを用いてインタフェースを管理するためのもので、インタフェース開放時に呼び出すDeleteModuleを持っています。
普通にIFilterとIFilterFactoryを別のクラスで継承して実装すれば問題ないのですが、面倒なのでIFilterとIFilterFactoryを多重継承する一つのクラスで実装しようとしました。
IFilterはリクエストごとに生成・開放され、IFilterFactoryはサーバ起動時に生成、停止時に開放されます。

同じクラスで実装する場合、IFilterは当然個別のインスタンスを持たず、IFilterFactoryと同じインスタンスを共有しますから、IFilter::DeleteModuleは何もしない空メソッドで、IFilterFactory::DeleteModuleは、実装クラスを削除するメソッドでなくてはなりません。

つまり、多重に継承された同じ基底クラスの同じ名前のメソッドに別の処理を行わせる必要があります。
調べてみると、C++ではこのような事は出来ないようです
IModuleの継承にvirtualを使えば、IModuleは一つしか継承されなくなりますが、これでは問題は解決しません。

そこで考えたのが、IModule::DeleteModuleにインタフェースのポインタを引数として持たせる方法です。
具体的には、以下のような感じです。

//! ベースクラス
class IModule
{
public:
    //!	削除
    virtual void DeleteModule(void *ifPtr) = 0;
};

//! インプリメント
class CFilterImplement : public IFilterFactory, public IFilter
{
    //!	削除
    virtual void DeleteModule(void *ifPtr)
    {
        // 削除対象がIFilterFactoryの場合のみ削除
        if(ifPtr == (IFilterFactory*)this)
            delete this;
    };
};

この方法なら、利用側が削除時に削除対象を引数に入れて呼び出せば、実装側で判断できます。
ただ、これでは、もはや手段が目的になってしまっていて、無駄に複雑さと分かりにくさが増しています。
考え直し、最終的にはIModuleを継承するのをやめ、それぞれFilterDeleteとFilterFactoryDeleteと言うメソッドを用意しました。
結局、ラッパ側を改良したほうが実装が楽で、人間にも分かりやすい構造になりました。

ps.
「さくじょじ」→「裂く女児」と変換されるこのIMEは異常だと思う・・・
ネットカフェのPCだけど。