2014年6月21日 (土)

自作のXmlWriterをXslCompiledTransformにわたす

XmlWriterを自分で直接実装することはなくても、XmlTextWriterのサブクラスを作るのは、よくやりますよね。

あれ、やらない?変だなぁ。じゃぁ、みんな、こんなの作るときどうやってるんだろう。「セルの中で改行したいときには、コンテント(内容)中で普通に改行するんじゃなくて、
って書きなさい」っていう謎仕様なんですけど。あ、いや、上のリンク先には書いてありませんが。でも、そうなの。仕様なの。

単にLinq to XMLで書き出すだけなら何も問題ないんですけど、XSLTで処理した結果を自作XmlWriterで出力しようとしてハマったので、その回避方法を書いておきます。

自作したXmlWriterクラスのインスタンスを、いったん XmlWriter.Create(XmlWriter, XmlWriterSettings) にわたし、返ってきたインスタンスを XslCompiledTransform.Transform にわたす。これでOK。

ただし、注意点がひとつだけあって、それは、自作XmlWriterクラスのSettingsプロパティは、常にnullを返すようになっていなければいけない。まぁ、XmlWriterのSettingsのデフォルト実装は一律nullを返すので、そのままにしておけばいいだけなんですけど。

えー、なんちゅうか、ですね、これに気づくまで2ヶ月悩み続けた、という。XslCompiledTransformとXmlWriterの間には、公開されていない秘密のインターフェースがあって、一般人が使えるXmlWriterのサブクラスはそのインターフェースを使ってないので、XslCompiledTransformと組み合わせると激しく誤動作する(例えば、xsl:output で doctype-public 指定してるのに DOCTYPE 宣言が生成されないとか)というのはすぐに気づいたが、回避方法がわからなかったんですよ。まさか、XmlWriterSettingsに秘密の(internalの)プロパティがあって、そこにdoctype-publicが入っているので、XmlWriter側の責任でそのinternalのdoctype-publicを読み取って、XmlWriter.WriteDocTypeが呼ばれなくても正しいタイミング(つまり、文書ルート要素に対するWriteStartElementの直前)で勝手にDOCTYPE宣言を作らなければならない、なんていう仕様、これをなんとか実装しようとしていたんです。2ヶ月間。

でも、それはハズレの道だったんですね。

たぶん、マイクロソフトも、こんな仕様をクラスライブラリの外側で実装できるわけがないことはわかっていて、ちゃんと救済策を用意してくれていたわけです。それが、っていうか、そのためだけの目的で作られたと思われる XmlWriter.Create(XmlWriter, XmlWriterSettings) なわけです。

だったら、どうしてちゃんとドキュメントに書いておいてくれないんだ、せめてヒントだけでも、って思うわけですが、答えがわかってからドキュメント読んだら書いてありました。本当にヒントだけですが。

This method allows you add additional features to an underlying XmlWriter object.

訳:このメソッドにより、元のXmlWriterオブジェクトに追加機能を加えることができます。

この「additional features」っていうのが、XslCompiledTransformとXmlWriterとの間の非公開の様々なインターフェースの処理(上でかいたDOCTYPEの件以外にもたくさんある様子)ということを控えめに表現したものらしい。

うーん、なんちゅうかなぁ。

2014年6月14日 (土)

使えないハッカー

ハッカーというのはツールが大好きだ。何かというとすぐにツールを作る。

ちゃんとしたハッカーは、普通に手作業でやると30分かかる作業を数秒で実行するスクリプトを5分で書いたり、普通にやると1日かかる作業を30分で終わらせるための専用ツールを1時間で作ったりする。

「だって、そのほうが結局早いし、変なミスもしないでしょ。」というわけだ。

ダメなハッカーは、30分の作業を一瞬で実行するスクリプトを5分で書くつもりで書き始めるが、なかなか思ったような動作にならず、なんとか一通り動くスクリプトができるまでに30分以上かかっちゃったりする。それでも一部不具合が残っており、結果のファイルをエディタで一部書き換えなければならなかったりする。

ダメじゃないけど使えないハッカーは、5分でスクリプトを書きあげるのだが、「この程度の処理で3秒もかかるなんて、やっぱり(ピー)じゃだめだな。Cで書き直そう」とか言って書き直し始める。30分ほどで動くようになるが、「処理は一瞬で終わるようになったけどメモリ食いすぎだな。しかも、こことここで同じような処理を二度書いてるのもみっともないな。」などと言ってさらに改良をはじめ、結局満足できるツールになるまでに数時間かける。(で、その「完成」したツールは、当然ながらもう二度と使わない。)

2014年6月 1日 (日)

食洗日記をひさしぶりに更新

じつは2ヶ月近く食洗日記を更新していなかったのですが、先ほどまとめてアップロードしておきました。

つまり、写真自体は撮影してあったのです。

まぁ、誰も待っていなかっただろうとは思いますが。いちおうお知らせでした。

2014年5月 3日 (土)

LINQ to XMLには「空のテキストノード」が存在する

Linq to XMLを使ってXMLツリーを生成し、XElement.Save() でXML文書として書き出すときの話。new XText("") ってやると、内容が空文字列であるXTextオブジェクトができる。しかも、これを要素の(唯一の)内容にしても、自動的に消えたりせずに保存される。

例を示そう。

new XElement("foo", new XText("")).Save("foo.xml");

とやると、

<foo></foo>

という文書ができる。(実際にはXML宣言が付くが省略。)

他方、

new XElement("foo").Save("foo.xm");

だと

<foo />

になる。

つまり、空のテキストノードがあるのか、何もないのかが区別されているのだ。

これは、本来のXMLのインフォセットというかデータモデルというか、それとは違うわけで、気持ち悪いのだが、しかし、これを積極的に利用すると、シリアライズ後に空要素を<foo />にするか<foo></foo>にするかをプログラムで(個々の要素インスタンスごとに)選べる、ということでもある。

それはそれで便利な気がする。

※ 因みに、マイクロソフトにはめずらしく、このことはドキュメントに明記されている。私はずっと気づいてなかったけど。

2014年4月25日 (金)

Visual Studio ExpressでNSISを使う

ProjectのPropertiesのBuildEventsのPost-Build event command lineに、こう書く。

<pre>if "$(Configuration)"=="Release" "C:\Program Files (x86)\NSIS\makensis.exe" install.nsi</pre>

Run the post-build eventはOn successful buildのままで。

これで、Visual StudioでReleaseビルドすると、自動的にNSISインストーラーが出来るようになります。

目からウロコというか、コロンブスの卵というか、実は単に頭が悪いだけかもしれませんけど、数年間、ずっとあきらめて、ビルドした後、手でコマンド打つということを繰り返しておりました。ははは。

なお、install.nsiは、プロジェクトフォルダに普通のソースファイルと一緒に置いておくわけですが、Copy to Output Directory を Copy if newerにしておきます。

2014年4月 9日 (水)

USB 3.0 のUSBメモリ

SEITECという、少なくとも私は聞いたことのないメーカー(ブランド)の、C102という、なんだかやる気が感じられない名前・型番のUSBメモリをあきばおーで購入。128GBで6534円(消費税8%込み)ということで、安かったので。

この容量で最安値ではなかったが、最安値の品が「50MB/s」と主張しているのに対してこちらは「UP TO 210MB/s」と書いてあり、差が数百円だったのでこっちにしてみた。

だったら、ベンチマークしなきゃだめでしょ、ということで。

そしたら、こんな感じだった。(画像クリックで拡大。)

Usb3

うむ。これはしっかり速いんじゃないかな。シーケンシャルアクセスに限れば、秋葉原に出回りだした最初のころのSSDよりはずっと速い。512KB単位のランダムアクセスで、リードとライトの差が大きすぎるような気もするが、これはバッファ(キャッシュ)が小さいとか、そういうことなのかなぁ。

2013年11月17日 (日)

SAT Solverでナンプレを解くのにかかる時間

昨日、「ナンプレを解くのにかかる時間」についての質問をいただいた。質問者の意図を正確に把握できているかどうかわからないが、私はこの問いを「大抵の問題が2~3秒で解けるのはわかったが、とても難しい問題のときに、最大どれくらいの時間がかかるのか」という問いかけだと受け取った。

そこで昨日は、以下のようなことを答えとしてお話した。

  • 実は、よくわからない。
  • それなりにいろいろな問題を試しているつもりだが、どの問題も3秒程度で解けている。早く解ける問題はたまにあるが、特に長い時間がかかる問題は経験していない。だから、たぶん、全ての問題が3秒以内で解けるのではないかという気がしている。
  • 人間がパズルとしてナンプレで解くときの問題の難しさと、それをSATに変換してSAT Solverが解くときの難しさは、同じではないようだ。開発の初期のころに、(あまり多くない問題を)SAT4Jで解くのにかかる時間を測定しながら評価していたのだが、ナンプレ本が示す問題の難易度と、SAT Solverで消費した時間とが、逆転する(つまり、難易度が高い問題の方が短い時間で解ける)ことがあった。
  • どういうときにSAT Solverで解くのに時間がかかるのか、私にはよくわかっていないので、最悪でどれくらい時間がかかるのかも、私にはわからない。ひょっとすると、SAT Solverに向かないナンプレの問題というものがあって、それを「ナンプレ破り」で解こうとすると、いつまで待っても解き終わらずに固まってしまう(ANR)ことがあるかもしれない。そう思うと怖い。

この件について昨夜帰宅途中でいろいろと考えたのだが、まず、SAT Solverで解きにくいSAT問題について調べるべきだと思い、ぐぐってみたところ、こんな記事を発見した。

http://d.hatena.ne.jp/hzkr/20090209

この記事自体は、「ランダム生成したSATの問題が解を持つ確率」について検討し、「式(項)の個数 / 変数の個数」という値が4.27の辺りだけ、解を持つかどうかが予測できなくなる、という事実を紹介しているのだが、最後の方にこんなことも書いてある。

4.27付近は(中略)SATを解くアルゴリズムにとってとても難しいゾーンということになります。(中略)ヒューリスティックを駆使して効率的にSATを解くアルゴリズムは、式長/変数数比がこのゾーンの外ならとても効率的にSATを解けるらしいです。この4.27ゾーンだけが鬼門。

4.27ゾーンというのは、具体的には 3.5 ~ 5 くらいの範囲らしい。

ほほう、と思って、ナンプレの問題からできるSATの問題について、この比を計算してみると、11745 ÷ 729 ≒ 16.1 。めっちゃ大きい! どうも、ナンプレを変換したSATの問題というのは、大抵、SAT Solverで効率的に解くことができるものになるみたいです。

2013年11月16日 (土)

プレゼン資料掲載しました

本日無事に、日本Androidの会横浜支部の第18回定例会で、「OpenCVとSAT4Jを使ってナンプレに挑戦してみた」と題してしゃべることができました。

参加者の皆さんから、いろいろと参考になるフィードバックもいただきました。ありがとうございました。

使ったプレゼン資料をSlideShareに載せましたので、興味のあるかたはご覧ください。

http://www.slideshare.net/AlissaSabre/opencvsat4j

2013年11月15日 (金)

メモリ管理の話の続き

先日の記事の最後の部分に書いた、Java VM がネイティブヒープも管理するという話の続き。

Javaからネイティブコードを呼び出すときにはjniという仕組みを使う。

jniでは、呼ばれる側(ネイティブコード側)が、Javaからjniで呼ばれるということを全力で意識したコーディングが必要になる。ただそれは入り口だけで、内部の処理は普通のC++(または、C++から呼べる任意の言語)のライブラリとしてコーディングすればいい。メモリ管理の仕組みも普通のC++と全く同じだ。

Sun (Oracle) のJVM用にjni用ネイティブコードを開発するときには、コンパイラとかCライブラリとかは、特にJavaを意識していない、普通のCコンパイラ、普通のCライブラリを使う。だから、メモリ管理の実装(例えばmallocの内部処理)は、JVMとは無関係なものになる。

でも、Androidの場合は、Android用のC/C++開発環境というのは、AndroidのJVM (Dalvik) から呼び出されるjniネイティブコードを作る専用の環境だ(よね?)だから、そこで提供するCライブラリは、APIは普通のCライブラリと互換でも、内部の処理が普通のCライブラリと同じである必要はないと思うのだ。例えば、malloc() という関数が、内部で Dalvik のAPI を呼んだりしても一向に構わない。

そういう仕組みにすれば、ネイティブコードが使うネイティブヒープの利用状況を、Dalvik が正確に把握することができて、ネイティブコードが malloc() を呼んだときにネイティブヒープが不足していたら malloc() がすぐにNULLを返さずに Dalvik が GC する、なんてことも可能だろうと思ったのだ。

本当にそうなっているのかどうか、Dalvik も NDKのCライブラリも、たぶんソースが公開されているんだろうから、自分で調べればわかると思うのだが、調べていない。単に妄想しているだけなのだけれども。

あと、.NET の CLR はやっている、と書いたのは、上に書いたような malloc がGCを起動する、というものとは別の仕組みだが、memory pressure のことを意識していた。

JVMに比べると、.NET の GC は、非常にたくさんの(それなりにヤヤコシくて使うのが難しい)APIを公開しているのだが、その中に memory pressure という機能がある。これは、ネイティブヒープの利用状況を、GC に教える仕組みだ。

.NET ではネイティブコードの呼び出しは P/Invoke という仕掛けで行う。これは、jniと違い、呼ばれる側(ネイティブコード側)は.NET からの呼び出しであることを一切意識せず、逆に呼び出す側(マネジドコード側)がネイティブコードの呼び出しを意識することになっている。このため普通は、jniではネイティブ側に書くいわゆるグルーコードは、P/Invoke では .NET 側で書く。

このとき、P/Invoke でネイティブコードを呼び出す直前とか戻った直後とかに、ネイティブ側で割り当てたメモリの量をGCに教えるのだ。新たに割り当てたときには AddMemoryPressure、解放したときには RemoveMemoryPressure というメソッドを呼ぶことになっている。両メッソドが適切に呼ばれていれば、CLRはネイティブメモリ利用状況を把握することができて、適当な上限値を越えるとGCを動かす、ような処理を行う、らしい。(実は、この辺り、正確な記述をみつけていないのだが。断片的な情報を組み合わせると、こんなところだと思う。)

この手法は、さきほど妄想した「ネイティブコードはmallocを呼ぶだけでOK」に比べると、余計な手間がかかるし、正確性にも劣るが、それでも、Sun JVMの「ネイティブヒープの使用量には一切関知しません。以上。」みたいなスタンスよりは大分いい。

2013年11月11日 (月)

約411年前のある日

「約411年前のある日」っていう表現はおかしいと思う。「411年」と正確にわかっているのだから「約」じゃないはず。「約400年前」なら妥当だが。

«LTやります