2008年4月27日日曜日

GCCを使ったJNIなどのDLLでSSE命令を使うときの問題点

JavaからJNIでDLLを呼び出せるので、JavaVMで処理に時間がかかりそうな処理をC/C++で開発してみようと思うのは普通だと思うし、どうせならMMXやSSEなどで高速に演算させたいと思うこともある。
GCCには、MMX、SSEなどの命令を呼び出すためのAPI(__builtin_ia32_hogehoge())があるので、これらを使えば、わざわざアセンブラのお世話にならずとも実現することが可能である。ということで、JNIなDLLをSSE命令を使って開発するのであるが、これが一筋縄に行かないようなのである。

SSEには、メモリー上のデーターをアクセスするための命令に128bitアライメントされているデーター用とそうでないもの用の2つがある(MOVAPSとMOVUPSなど)。要するに、128bitアライメントされているほうが高速にアクセスできるということであるが、もし、アライメントがされていないデーターにアクセスすれば、いわゆるソフトウェア割り込みが発生してアプリケーションエラーとなってしまう。
これらのアライメントを前提とする命令を使うには、コード上データーがアライメント上にあることが保証されているなければならないということになる。

普通、C/C++でプログラムするときには、データーがメモリ上どう配置されるかということを気にすることなく、もし、なんだかの制約があるとすれば、コンパイラーが何とかしてくれる(それ用のオプションをつけて実行するとか)ことにしている。
ただし、デフォルトでない特殊なアライメントなどを利用したい場合には、GCCでは__attribute__((aligned(n)))のような属性をつけられる。これは例えば、char data[256] __attribute__((aligned(16))) = {0};のように使うのだが、この例では、このchar配列を128bit(16byte)上に配置するようにしていている。

ただし、このように書けるのはC/C++上での静的な変数に対してであり、動的に得る場合(mallocやnewで生成する領域)では、このような属性では制御できない。ただ、C/C++の場合、ポインターはメモリーアドレスそのものであると考えられるので、ポインターをずらして、合わせてしまうという事が可能である(本質的にはAPI側でなんとかしてほしいところだが)。

このようにすれば、SSEでのアライメントの問題が解決するということにしたいのだが、実際にはそういう訳にはいかない。というのが、本題であったりする。
ここで根が深いのは、基本的にはコンパイラーのオプションや属性の指定で解決されているべきものが、実際にはアライメントされていないということである。
アセンブラ以外でアライメントなどを考慮しなくてはいけないのは、C/C++であるとしてでもおかしな話であると思うが、CPU固有の命令を使おうとしているので、なんだかの特殊な処理が必要であると考えても仕方がないかもしれない。確実に動くコードが書ければ、それはそれで問題ないといえるだろう。

ところで、C/C++で変数といわれるものは2つあり、それはいわゆるstaticで宣言されるものと、関数やブロックのなかで宣言されるいわゆるauto変数といわれるものである。
staticな変数は、モジュールがロードされるときに確保され、その領域はモジュールがなくなるまで存在する。この変数は、その領域のアドレスをどこかに保持し、領域内の相対アドレスをコード内に持って、それを足してアドレスを確定してアクセスする。
ここでアライメントを保持するには、領域の先頭のアドレスをアライメントされるように配置して、相対アドレスもアライメントを保持するように割り当てるようにする。
コンパイラーでオプションや属性で指定するアライメントは、相対アドレスにを指定するものであり、領域のアドレスはモジュールのロードにアライメントされて確保されるので、問題なく使える。

auto変数は、スタックといわれる領域に確保される。これは、関数が呼び出されるときのスタックのアドレス(スタックポインター)を基に相対的に割り当てられる。
autoな変数のアライメントは、staticと同様に相対アドレスは、コンパイラーが割り当てて、スタックは、モジュールのロード後、最初の関数を呼び出すときにアライメントされるように割り当てられるようになっている。その後、呼び出される関数もアライメントするように配置することによって、アライメントを維持するようにしている。
そうすると、staticな変数と同様にauto変数も問題なく動くはずなのだが…。

最初に述べたのだが、DLLでSSEを使うときの問題があるということなのだが、それは、DLLのスタックは、呼ばれる元のモジュールのスタックを引き継いで利用する。これは、C/C++で関数を呼び出すために必要な仕組みである。

もし、C/C++コード上でアライメントが不明であれば、アライメントを前提としない命令を使えばよいのだが、コンパイラーはauto変数に割り当てられたSSE用の変数を、アライメントされているものとして、読み書きするような命令を出力するのである。
ということで、おわかりであると思うが、DLLを呼び出すモジュールがアライメントを維持しないようにスタックを使っている場合、DLLでアライメントを保持するようになっていても、うまく動かないということになるわけである。それは、JNIがJavaVMから呼び出されるときも起きていて、JNIのDLLでSSE命令を使おうとすると、ほとんどうまくいかない(うまくいくのは、SSE用の変数がすべてレジスタ上に配置されたときか、運良くスタックがアライメントされているときだけ)。

どうやら、このことを問題だと思っている人が多く居るらしく、GCCでこれを回避するオプションを作ることが想定されているようだが、まだ実装されていないもよう。ましては、MinGWでの移植にも時間がかかるため、おそらくMinGWのGCC環境では、うまく動くコードをはくコンパイラーはしばらく先になるだろう。

2008年4月14日月曜日

PHPでAJAXをやってみる

PEARにHTML_AJAXというパッケージがあるが、それを使わずにいわゆるAJAXをやってみるということです。
とりあえずということで、基本的なことだけかもしれませんが、いちおう動いているコードを書いてみます。PHPとかいっている割には、大半がJavascriptな内容かもしれません。

AJAXとはなにか?ということは、世の偉い人があちこちで解説されているので、ここでは説明しませんが、基本的には「非同期でサーバーから情報を取る」というのと、 「サーバーがXMLドキュメントを送る」、「XMLドキュメントをJavascriptで処理をしてブラウザに表示」という3つであると思って書きます。

非同期でサーバーから読み出す

まずは、Javascriptの「XMLHttpRequest」のオブジェクトを作成して、それを使ってデーターを読み込むという手順になります。XMLHttpRequestを作成するコードは以下のような感じ(どこかのコードのパクリかもしれないが…)。

function createHttpRequest(){
 if(window.ActiveXObject){
  try {
   return new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
   try {
    return new ActiveXObject("Microsoft.XMLHTTP");
   } catch (e2) {
                return null;
            }
         }
    } else if(window.XMLHttpRequest){
        return new XMLHttpRequest();
    } else {
  return null;
    }
}

という関数を定義して、var request = createHttpRequest();とすれば、XHTMLRequestのオブジェクトが得られる。

次にデータを取得するわけだが、クラス化されたJavascriptで処理をするために以下のような関数を作ってみた。

function sendRequest(obj, callback, url) {
 var request = createHttpRequest();
 if (request == null) return false;
 request.onreadystatechange = function() {
  callback.call(obj, request);
 }
 request.open('GET', url, true);
 request.send(null);
 return true;
}

データーの受信は、非同期で行われるため、コールバック関数を用意して、読み込みが完了したら、コールバック関数が呼び出されて、そこからデーターを読み取るという処理になる。
この関数を使うためのクラスは、以下のようになる。

function MyClass(){
 this.callback = function(request) {
  if (request.readyState == 4) {
   if (request.status == 200) {
    var xmlDoc = request.responseXML;
    //このxmlDocから情報を読み取る
   }
  }
 }
}

ちなみにrequest.readyStateというのは、1から4までが設定されて呼び出されるのだが、4というのが完了したということになる。あと、request.statusを確認するも必須で、これはHTTPのリザルトコードになっている。すなわち、200で正常に通信したということであり、そのほかはなんだかのエラーということになる(エラー処理はここでは省略…)。このクラスと先ほどの関数を使ったコードは以下の通り。

var url;//データーを持ってくるためのURL
var obj = new MyClass();
sendRequest(obj, obj.callback, url);

この関数は、MyClassの中で定義してしまってもよいかと思います。そのときは、objはthisになります。

サーバー側の処理

サーバーから送るデーターはXMLになっていれば、Javascriptで値が取り出せるということになるわけだが、例えばa=1とt=testというデーターを送りたいのであれば、

<?xml version="1.0"?>
<result>
 <a>1</a>
 <t>test</t>
</result>

というXMLを送ればよいということになる。ちなみにコード系がUTF-8以外ならば、versionのあとにcharsetを設定しなければならないだろう。
要するに、このようなXMLを返すのであれば、なんでもいいのであるが、PHPでなんだかの処理をして返すということにした場合、PHPのソースのどこかで、HTTPヘッダを設定しなければならず、header("Content-Type: text/xml");と書けばよいということになる(application/xmlというタイプもあるが、使い分けはよくわかってません…)。

XMLドキュメントをPHPでどう生成するかということになるわけだが、ごりごりと自分で上記のようなXMLを出力するコードを書いて、送るのでもよい。そのへんは規模にもよるとは思うが、PHPに組み込まれている関数を使ったほうがよい場合もあると思う。
ここでは、本来の使い方ではないかもしれないが、「DOM XML 関数」というPHPの組み込み関数を使うことにする。具体的には「DOMDocument」というクラスのオブジェクトを生成して、そこにXMLのデーターをセットして、saveXML();というメソッドでXMLの文字列を得る。それをechoして、クライアントに送るという手順になる。
先ほどのXMLと同じ内容を出力するコードは以下のようになる。

header("Content-Type: text/xml");
$doc = new DOMDocument();
$resultElement = $doc->createElement('result');
$doc->appendChild(resultElement);
$aElement = $doc->createElement('a');
$aElement->appendChild($doc->createTextNode('1'));
$resultElement->appendChild($aElement);
$tElement = $doc->createElement('t');
$tElement->appendChild($doc->createTextNode('test'));
$resultElement->appendChild($tElement);
echo $doc->saveXML();

Javascriptでデーターを得る

まず、いきなりいい訳だが…、以下の手順はちゃんと動いているものの正しいかどうかはあまり自信がない。ネットでいろいろ調べてみたものの、いくつかのコードが見受けられた。
とりあえずは、取得したXMLドキュメントに同じタグ(エレメント)が存在しない(上記の例では<a>1</a>という'a'タグがひとつしかない)というのを前提とする。

以下のようなJavascriptの関数を用意してみた。

function getElement(parent, name) {
 return parent.getElementsByTagName(name).item(0);
}
function getNodeValue(element) {
 return element.childNodes.item(0).nodeValue;
}

この関数を使って、先ほどのXMLドキュメントのオブジェクト(xmlDocという変数)から"a"という値を得るコードは以下の通り。

var aElement = getElement(xmlDoc, 'a');
var a = getNodeValue(aElement);

あとは、これらの得られたデーターをブラウザ上に表示するわけだが、これはいわゆるDOMといわれている仕組みを使って表示するのだが、ここではさらに長くなりそうなので割愛させていただく。

というわけで、とりあえずながらも、PHPを使ってAJAXをやってみた。あとは、いわゆる配列のようなデーターをJavascriptのXMLDocumentから取得できるようにする必要があると思う。これについては、いずれ書きたいと思う。

はてな

はてなスターをつけてみたよ
↑と書いてみるテストwww

はてなから訪問される方が多いので、はてなに登録して、はてなスターをつけてみました。
ちなみにBloggerもOpenIDに対応しているので、はてなのIDでコメントなどができるようです(実際にやってないので、どんなもんだかは不明)。

もっと、ちゃんとした記事を書いていかなくては、いけないなぁと感じております。
おしえて君以外のコメント等は歓迎ですので、よろしくおねがいします。

2008年4月1日火曜日

EclipseとMinGWでJNIモジュールを開発する

ずいぶん前の記事で、JNIモジュールをEclipseでビルドやデバッグする方法を書いたのだが、久しぶりにJNIモジュールを作ってみようとしたら、同じようにはまってしまったw。今回は、はまったついでに、手順についてまとめることにする

前提としては、WindowsでEclipseを使って、JNIモジュールを作るということで、コンパイラはMinGWのG++を使う。ネイティブ側はC++で書くことにする(Cでもそれほど違いはないはずだが)。
あとは、JDKドキュメントにあるJava Native Interface 仕様とMinGWのFAQぐらいは読んでいるという前提になる。EclipseはJavaとC/C++の開発環境(CDT)の両方がインストールされているものとする。

プロジェクトの作成

まずは、Javaのプロジェクトを作成する。Javaの方はJDKのドキュメントにあるとおり、nativeなメソッドを持つクラスを作成し、static{}の中でSysytem.LoadLibrary();を記述する。LoadLibraryの引数は、パッケージがある場合、"package_class"のように"."(ドット)を"_"(アンダースコア)に置き換えた文字列にする。
例)myproject.testというパッケージでクラスがMyclassの場合、"myproject_test_Myclass"となる

C++のプロジェクトを作成するのだが、そのときにプロジェクトタイプは、"Shared Library"を指定する。蛇足かもしれないが、C++でJNIのAPIを呼び出すときには、JNIEnvのオブジェクトのメンバー関数を呼ぶ形になる。例えば、JNIのAPIがvoid hoge(JNIEnv* env, jstring str);という風にドキュメントに書いてあったら、C++では、env->hoge(str);という風に呼び出す。
次にビルドの設定で、出力ファイルはLoadLibrary();で設定した文字列に拡張子".dll"が付くファイルが出力されるように指定する。makeコマンドの設定のところで、MinGWであれば、デフォルトを外して"mingw32-make -k"とする。
ツールの設定(g++などの設定)のところを設定する、C++のコンパイルの設定で、プリプロセッサ(-D)のところに"_JNI_IMPLEMENTATION_"というdefineを追加する。あと、インクルードパス(-I)にJDKにあるincludeとinclude/win32の二つを設定する。もし、JDK_HOMEとかの環境変数が設定されていれば"${JDK_HOME}\include"と"${JDK_HOME}\include\win32"となる。この手の環境変数を設定したいならば、環境変数のところで設定できる。
次にリンカの設定のところで、"-Wl,--kill-at"というオプションを設定するのであるが、適当なところがなさそうなので、コマンドラインのところで"g++"のあとにスペースを空けて設定する。リンクするライブラリ(-l)のところには、"jvm"を設定し、ライブラリの検索パスのところには、JDKにあるlibを設定する。JDK_HOMEを設定していれば"${JDK_HOME}\lib"となる。

javahを実行する設定

Javaのソースでnativeを含むクラスファイルをjavahコマンドに渡すと、JNIようのCのヘッダファイルを出力するのだが、javahをどうやって起動するのか?ということです。一度javahを実行すれば、ファイルが出来上がるので、DOS窓でコマンドを打ってもいいわけですが、きっと何度かコマンドを実行するハメになるので、どこかに書いておいて、簡単に実行できるようにしておきたい、と考えてみた。
その方法はいくつかあるが、とりあえず思いついたのは

  • Javaのプロジェクトにantを書いて、手で実行する
  • Cのビルド設定のステップビルドのところで、ビルド前のコマンドのところでjavahを書いて、ビルド時に実行する
  • ビルド前コマンドのころで、javahを起動するmakefileを書いて、ビルド時にmakeを実行する

といったところだが、どれか楽そうなのを選べばよいと思う。ちなみにantは、antを実行するjavaコマンドがJDKのものでないと、javahタスクは実行できないもよう。あと、ビルド前コマンドは、ビルドを実行するときに毎回起動されるので、場合によってはうざいかもしれない。少なくとも自動ビルド向きではない。
makefileを書いて、DOS窓から手動実行するぐらいが無難なのかもしれない…。

Javaの実行設定

nativeを含むクラスを読み込むと、LoadLibrary();でDLLをロードする仕組みなので、EclipseでJavaの実行を設定して、実行(Run)すればよい。ここでの問題は、DLLをロードしようとしたときにjava.library.pathというシステムプロパティに登録してあるパスの中から、DLLを探し出して、ロードするという仕組みになっているので、それらのパスに含まれてないと、実行時に失敗するということである。これを解決する方法もいくつか考えられる。

  • java.library.pathにあるパスにDLLをコピーする
  • 実行時にjava.library.pathをDLLが存在するパスに設定する(起動時オプションのVMの設定で"-D java.library.path=…"と書く)
  • 実行時にPATH変数(SolarisとかはLD_LIBRARY_PATH)にDLLが存在するパスを追加する

とりあえず、ここではデバッグをすることを目標とすることにして、実行時にビルドされたデバッグようのDLLのパスをPATH変数を追加するという方法をとることにする。
具体的には、実行時の環境変数の設定で、"PATH"と変数を追加して、値を「${workspace_loc:<Cのプロジェクト名>/Debug}」とする。このとき、環境変数は追加するの方にしておいたほうがよい。

この段階で、Javaプロジェクトのほうが実行できなければ、デバッグもできないので、普通に実行できるようにしておく。

JNIモジュールのデバッグ

実行(Run)で動いていることが前提として、デバッグをするのだが、あとはC側のデバッグの設定をする。
アタッチをするデバッグを定義して、アプリケーションのところには、Debugフォルダに出力されたDLLを設定する(普通は"Debug/<LoadLibrary();に設定した文字列>.DLL")。これで、設定は完了。
デバッグの手順は以下の通り。

  • デバッグしたいCのソースにブレイクポイントを貼る
  • JavaのLoadLibrary()が実行された後でかつ、デバッグしたいCの関数が呼び出される前のどこかにブレイクポイントを貼る
  • Javaをデバッグ実行する
  • Javaのブレイクポイントで止まったら、Cをデバッグ実行する(アタッチをする)。アタッチコマンドは"javaw"であるが、Eclipseも"javaw"で動いているので、間違えないこと。タスクマネージャをみて、EclipseのPIDを確認しておいたほうがよいかも。
  • Cの実行が止まるので、Cのモジュール(gdb/mi)を選んで、再開させて実行中(Running)の状態にする
  • Javaのモジュールを選んで、ブレイクポイントで止まっているJavaを再開させる
  • Cのほうがブレイクポイントで止まる

と、いった具合でちょっと面倒だがこういった感じになる。
JavaのブレイクポイントのところでLoadLibrary()のあと…といっているところは、LoadLibrary();のところにブレイクポイントを貼って、LoadLibrary()をステップ実行させるという方法が無難かもしれない(手順は増えてさらに面倒になるけども)。
あと、アタッチのあとで、コンソールにエラーのような文字列が現れるが、そのまま、実行を再開させても問題ないもよう。場合によっては、問題が生じるかもしれないが。
アタッチ直後にCのデバッグが停止したり、エラーを出力するのは、g++、gdbのバージョンなどで変わる可能性がある。

ということで、なんとかEclipseとMinGWでJNIのモジュールが開発できるようになった。上記にも述べたが、MinGWのバージョン(g++やgdbなど)によって、状況は変わる可能性がある。ここではMinGWは5.1.3を使っている。