External Objectで高速化

このところ冷え込みが厳しくてお布団から出るのがつらい日々が続いています。まあ、なんとか這い出してお仕事にでかけています。体冷やしちゃうと風邪もらっちゃったりし易くなるので、保温には注意しています。故あって風邪等引けない状態なモノですから。皆様も体調を崩さないようにご注意下さいね。インフルエンザも下火になったようだけどまだまだ油断は禁物だと思いますし。
先日校正に出したTEX編集のモノがまたもや真っ赤っかとなって返ってきましたwwwまあ、それは良いとして、割とコントロールしやすいスクリプトですから、そこそこ調整もやっちゃうのですが、さすがにIndesignのように自在にとは言いがたい訳です。皆さんはTEXの処理とかどうなさっているのでしょうか?この辺りに関しても印刷屋さん向けの情報は不足気味ですよね。せっかくの論文ですので、先生の納得なさるレイアウトに仕上げたいのですがなかなか大変なわけです…と、どうでも良い前置きはこれ位にしますね。
先日、Macintosh向けにFrameworkのテストを行った訳ですが、中途半端なものだったので分かりにくかった事だと思います。と言う事で、最後にちらっと書いていたbase64エンコードが良い見本となりそうなので解説してみようと思うんだ。Windows向けもちら〜っと書いているのでWinな人もチェックして下さいね。

今回はXcodeの操作から順を追っていってみるよw
ちなみにわたしはトラ使いなのでバージョンは2.5だよ。新しいのは正直わかりませんのであしからず。

b64_xcd1.png

まずは新規プロジェクトの作成から。こちらは上のようにCarbon Frameworkを選択。

b64_xcd2.png

次は、プロジェクト名を付けて完了をクリック。

b64_xcd3.png

すると、空のプロジェクトが出来上がるんだ。
ちなみにmain.cは編集しても良いし、捨てちゃっても良いんだ。今回はcodeを別の所で書いたので、コードに既存のファイルを追加しているんだ。

b64_xcd5.png

base64.cppはわたし自身の編集したコードだけど、SoSharedLibDefs.hとSoCClient.hはAdobeさんの純正ヘッダファイルだね。
これが無いとexternalObjectとして動作してくれないので必ずincludeして下さいね。それから、これってCS3には付いてないので添付しておきますね。

headder.zip

続いてWindows。環境はXPのSP2、VisualStudio2008でいきます。しかし、WindowsにはCSシリーズが入っていませんので、未検証なんです。とりあえずDLLを生成する所までご参考にw

b64_vstd1.png

Windowsの場合は新規プロジェクトの種類はC++のMFC DLLのテンプレートを選ぶ。

b64_vstd2.png

共有MFC DLLを使用する通常のDLLを選択して完了。

b64_vstd3.png

すると、要らないものがいっぱい付いてくるので全部捨てて、必要なものを読み込むんだ。
ちなみに、ソースはMacと同じものでOK。
見本ではb64Enc.cppってなっているけど、Macintoshで作ったbase64.cppと内容は同じなんだ。

b64_vstd4.png

で、既存のファイルを読み込んだりするとソースの現物を認識してくれなかったりするのでソースの存在するパスを教えてあげよう。

b64_vstd5.png

更に、デフォルトではプレコンパイルヘッダとか有効になっているのでこちらも設定を外そう。

以上でプロジェクトの設定はおしまい。
続いて、コードの解説を。

//base64.cpp

#include “SoSharedLibDefs.h”
#include “SoCClient.h”
#include <stdlib.h>
#include <stdio.h>
#include <fstream>

using namespace std;

#if defined ( _WINDOWS )
#pragma warning( push )
#pragma warning( disable : 4996 ) // Security warning about strcpy on win
#define strdup _strdup
#endif

#if MAC
#define unused(a) ( void* ) a ;
#else
void* unused( void* x ){ return x;} ;
#endif

const char *szB64 = “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=”;

static void Encode ( const char *inputFilename, const char *outputFilename )
{
    int iRtn = 76;
    int i;
    char c[3];
    ifstream istr ( inputFilename, ios::in | ios::binary );
    ofstream ostr ( outputFilename );

    i = 0;
    while ( !istr.eof() )
    {
        c[0] = c[1] = c[2] = ‘¥0’;
        istr.read ( c, 3 );
        if ( istr.gcount() == 3 )
        {
            ostr << szB64[ (c[0] & 0xfc) >> 2 ];
            i++;
            if ( i >= iRtn )
            {
                ostr << endl;
                i = 0;
            }
            ostr << szB64[ ((c[0] &0x03) << 4) | ((c[1] & 0xf0) >> 4) ];
            i++;
            if ( i >= iRtn )
            {
                ostr << endl;
                i = 0;
            }
            ostr << szB64[ ((c[1] & 0x0f) <<2 ) | ((c[2] & 0xc0) >> 6) ];
            i++;
            if ( i >= iRtn )
            {
                ostr << endl;
                i = 0;
            }
            ostr << szB64[ (c[2] & 0x3f) ];
            i++;
            if ( i >= iRtn )
            {
                ostr << endl;
                i = 0;
            }
        }
        if ( istr.gcount() == 2 )
        {
            ostr << szB64[ (c[0] & 0xfc) >> 2 ];
            ostr << szB64[ ((c[0] &0x03) << 4) | ((c[1] & 0xf0) >> 4) ];
            ostr << szB64[ ((c[1] & 0x0f) <<2 ) ];
            ostr << szB64[64];
        }
        if ( istr.gcount() == 1 )
        {
            ostr << szB64[ (c[0] & 0xfc) >> 2 ];
            ostr << szB64[ ((c[0] &0x03) << 4) ];
            ostr << szB64[64];
            ostr << szB64[64];
        }
    }
    ostr << endl;
}

extern “C” int b64String ( TaggedData* argv, int argc, TaggedData* result )
{
    char *getStr;

    getStr = argv[0].data.string;
    Encode( getStr, “/tmp_file.txt” );

    result->type = kTypeBool;
    result->data.intval = 1;
    return kESErrOK;
}

まず先の2ファイルは当然として、インクルードするものは

#include “SoSharedLibDefs.h”
#include “SoCClient.h”
#include <stdlib.h>
#include <stdio.h>
#include <fstream> 

標準ライブラリとIO、ファイルストリームのライブラリをインクルードしておく。忘れると怒られます。

using namespace std;

とありますが、その昔『C++では標準ライブラリのクラス/関数/変数には全て頭に「std::」を付けるように。』とお達しがあった訳で、その為にいちいち変数の頭に「std::」なんてくっ付けなければならなくなったのだけど、面倒なのでこれ一行書いておけば許してもらえるって事らしいんだ。

const char *szB64 = “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=”;

勢い余ってグローバルで定義しているけど、本当はファンクション内でOK。

static void Encode ( const char *inputFilename, const char *outputFilename )

ご覧の通りEncodeファンクションの引数は入出力ファイルの名称。リターンは無し。

    int iRtn = 76;
    int i;
    char c[3];

iRtnは折り返し文字数、iは文字数を数える為のカウンタ。c[3]の配列はそれぞれ1文字ずつ読み込む為の入れ物。

    ifstream istr ( inputFilename, ios::in | ios::binary );
    ofstream ostr ( outputFilename );

はそれぞれ入力ファイルストリーム、出力ファイルストリームの定義なんだ。入力ストリームはbinaryモードだね。
以下、ストリームを読み込み変換しながら出力していく部分。Javascript読める人ならなんとなく何しているか分かると思うんだ。

    i = 0;
    while ( !istr.eof() )

iの初期化の後のwhileはJavascriptでもおなじみの構文。istr(入力ストリーム)がeofを拾うまでループするって事だね。

        c[0] = c[1] = c[2] = ‘¥0’;
        istr.read ( c, 3 );

c{0]〜c[2]に対して「¥0」を代入しているけど、これはヌル文字って言って終端を表す特殊な値なんだ。
次の行はreadメソッドで入力ストリームから3キャラクタ読み出しているんだ。

        if ( istr.gcount() == 3 )

次のgcount()メソッドは入力ストリームから実際に読み出した値の数。ファイルのお尻で1文字とか2文字しか読み出せない場合はパディング処理が必要となるので読み出した数で処理を振り分けるわけだね。

            ostr << szB64[ (c[0] & 0xfc) >> 2 ];
            i++;
            if ( i >= iRtn )
            {
                ostr << endl;
                i = 0;
            }
            ostr << szB64[ ((c[0] &0x03) << 4) | ((c[1] & 0xf0) >> 4) ];
            i++;
            if ( i >= iRtn )
            {
                ostr << endl;
                i = 0;
            }
            ostr << szB64[ ((c[1] & 0x0f) <<2 ) | ((c[2] & 0xc0) >> 6) ];
            i++;
            if ( i >= iRtn )
            {
                ostr << endl;
                i = 0;
            }
            ostr << szB64[ (c[2] & 0x3f) ];
            i++;
            if ( i >= iRtn )
            {
                ostr << endl;
                i = 0;
            }

で、3文字読み出せた場合は8bit×3文字を並べて6bitづつに切り分けてキャラクタを拾う訳だ。一文字出力ストリームにキャッシュするごとに文字数が76文字に達しないかをチェックして、達していた場合は出力ストリームへ1行書きだすんだ。<< endlではストリームに出力してキャッシュをフラッシュすると言う動作をするんだね。
あとはJavascriptでやった時と同じ流れなので解説しません。ひとつ補足しておくと、1文字もしくは2文字しか読み出せなかった場合というのは、この4文字の範囲内では改行が有り得ないのでそのまま文字数をカウントすること無く通しているんだ。

extern “C” int b64String ( TaggedData* argv, int argc, TaggedData* result )
{
    char *getStr;

    getStr = argv[0].data.string;
    Encode( getStr, “/tmp_file.txt” );

    result->type = kTypeBool;
    result->data.intval = 1;
    return kESErrOK;

さて、こちらがExternalObjectのメソッドになる訳だけど、どうしてルーチンが切り分けてあるかって?データの取り回しがTaggedDataだと面倒だからだよ。ちなみに返されるのは常にtrueなんだ。本当は例外処理しないといけないんだけどね。問題なのは対象ファイルが存在しない場合だけど、Javascript側でチェックして下さいね。

かなりはしょってますが、あとはコンパイルかけるだけ。MacならFrameworkフォルダが、WinではDLLが生成されるわけだね。
割とシンプルなコードなので引っかかりどころは少ないはず。練習課題としては手頃なテーマだと思うんだ。

さて、ここで成果物の検証結果だけど、今までのbase64関連の各コードでの実行時のスループットを参考迄に。コードごとに対象ファイルが違うのは、統一しちゃうとまともな数値を得られないからなんだ(ちなみにAdobeのコードでは50MBのファイルをエンコードかけようとするとExtendToolKitがハングします。)

Adobeの中の人のコード 1.54KB/sec(対象ファイル/5MB)
ファイルストリームを利用したコード 51KB/sec(対象ファイル/50MB)
externalObject利用時 2.3MB/sec(対象ファイル/500MB)

という数値が出ています。ちなみにG5/2GHzデュアルコア、OS10.4.11での実行結果です。
こういった整数系の演算はIntelのチップっはとっても強いのでスループットはG5で処理しているわたしより良くなると思われるんだ。
誰かテストしてくれないだろうかねぇ???

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中