カテゴリー別アーカイブ: Haxe

NanoTestがマクロ環境以外のテストでのバグも、コンパイラ警告として出力できるようになりました

前回のNanoTestの記事 : テストで検出したバグをコンパイラ警告として出力してくれるHaxeライブラリ、NanoTestを公開しました

このNanoTest、バージョン1.0に更新しました!

これまでのNanoTest

Haxeのマクロ上でテストを実行して、検出したエラーをコンパイラ警告として出力すれば、IDEのコンパイルエラー表示機能を使って表示できるよ。コンパイルエラーと同じように扱われるから、FlashDevelopでもIntelliJ IDEAでも、エラー行の強調とか、エラー行へのジャンプ機能とかが使えるよ。

マクロでテストをすることの重大な欠点

Haxeには出力する言語特有バグとか、挙動の違いとかがあるよ。
あと、各プラットフォーム依存のライブラリとかを使うとマクロを使ってのテストができないよ。

新しいNanoTest

新しいNanoTestではマクロ以外で実行したテストの結果をIDE上に出力することができるようになりました。これで、プラットフォーム依存な内容のテストも簡単にできるようになります。

NanoTestOnFlashDevelopNanoTestOnIntelliJIDEA

しくみ

  1. ターゲットのプラットフォームへコンパイルする
  2. ターゲットのプラットフォーム上でテスト実行して結果をテキストファイルに出力する
  3. マクロでファイルを読み込んで、エラーの情報があれば、コンパイラ警告として出力する

これだけです。とってもシンプル。だけど強力。

これにより、出力対象がNekoだろうと、C++だろうと、C#だろうと、Javaだろうと、JavaScriptだろうと、FlashだろうとIDE上でテストのデバッグができるようになります。

インストール方法

以下のコマンドを入力

haxelib install nanotest

使い方

Githubのsampleフォルダに、FlashDevelop用、Intellij IDEA用のサンプルプロジェクトがあるので、それをみればどうやって使えばいいかはわかると思います。

Nanotest | Github

一応、簡単に解説。

NanoTestRunner.readResult

バージョン1.0で新たに追加されたNanoTestRunner.readResultをマクロから呼び出すことでテキストファイルの内容にしたがって、IDE上でのデバッグが可能になります。

具体的には、hxmlで以下のような指定を行います。

-lib nanotest
--macro nanotest.NanoTestRunner.readResult(テスト結果ファイルのパス, ソースのパスの配列, ラベル)

“テスト結果ファイル”というのは、テストのときに標準出力される内容をファイルにリダイレクトしたものです。NanoTest, haxe.unit, MUnitのNeko出力の出力形式に対応しています。

“ソースのパスの配列”は、テストのコンパイル時に-cpで指定したディレクトリを配列で指定してください。

“ラベル”は任意の文字列です。プラットフォーム名などをラベルにしておくと失敗の警告にラベルが表示されるので、IDE上で表示する際にどのプラットフォームで失敗したのかわかり安くなります。

Haxe/JavaScriptの場合

# test_js.hxml

#1. JSターゲットでコンパイル
-main sample.TestSample
-js bin/test.js
-lib nanotest
-cp src
-cp test
-debug

#2. node.jsで実行して、結果をファイルに出力
--next
-cmd node "bin/test.js" 1>bin/report_js.txt

#3. 出力したファイルをマクロで読み込んで、テストの情報を読み取る
--next
-lib nanotest
--macro nanotest.NanoTestRunner.readResult('bin/report_js.txt', ['test', 'src'], 'JS')

Haxe/Flashの場合

Flashのデバッグをする場合は、flashlog.txtを使用しますので、Flashのデバッグプレーヤーが必要になります。

# test_swf.hxml

#1. SWFに出力
-main sample.TestSample
-swf bin/test.swf
-lib nanotest
-cp src
-cp test
-debug
-D fdb

#2. Flashを起動して、flashlog.txtのテスト結果をコピー。
--next
-cmd swf_debug bin/test.swf bin/report_swf.txt

#3. flashlogのコピーから結果を読み取り
--next
-lib nanotest
--macro nanotest.NanoTestRunner.readResult('bin/report_swf.txt', ['test', 'src'], 'Flash')

Windows用のFlashを起動するバッチファイル(swf_debug.bat)。test_swfと同じ階層に置く。

:: swf_debug.bat

"C:\Program Files (x86)\FlashDevelop\Tools\flexlibs\runtimes\player\11.9\win\FlashPlayerDebugger.exe" %1 
cp "%APPDATA%\Macromedia\Flash Player\Logs\flashlog.txt" %2

MUnitとの連携

NanoTest v1.0のしくみなら、テスト情報の含まれるテキストファイルさえあればコンパイラ警告として出力が可能です。つまり、実際のテストを行うライブラリがNanoTestでなくても構わないわけです。NanoTest v1.0では、MUnitのテスト時に標準出力される失敗行の情報から、コンパイラ警告を出力する機能がついてます(Neko出力にのみ対応)

1. 標準出力をテキストファイルにリダイレクトするようにMUnit実行。

haxelib run munit run 1>bin\test_result.txt

.munitはこんな感じ↓

version=2.1.0
src=test
bin=bin
report=report
hxml=munit.hxml
classPaths=src
resources=resources
templates=null
coveragePackages=null
coverageIgnoredClasses=null

2. 出力されたテキストをNanoTestで読み込み

以下のような、hxmlを実行する。

# nanotest.hxml

-lib nanotest
--macro nanotest.NanoTestRunner.readResult('bin/test_result.txt', ['src','test'], "MUnit")

これで、MUnitでのテスト内容を、IDE上でデバッグできます。

そのほかの変更

パッケージの変更

パッケージ名が変わりました。

shohei909.nanotest.* -> nanotest.*

メソッドチェーンで、テスト失敗時に詳細情報を出力できるように

class SampleCase extends NanoTestCase {
    public function testBasic() {
        var a = [1, 2, 3, 5];
        var b = [2, 2, 3, 3];
        for (i in 0...a.length) {
            assertEquals(a[i], b[i]).label(i);
        }
    }
}

assertから始まるメソッドから、.label()にメソッドチェーンでつなぐと、追加の情報が出力されます。

上の例では、以下のような出力がされるので、失敗したのが配列の0番目と3番目の要素だったことを知ることができます。

Test failed : expected 1 but was 2 [0]
Test failed : expected 5 but was 3 [3]

-D result_exit_code

出力ファイルをNanoTestでreadResultする際に、result_exit_codeのフラグをつけると、テスト失敗時に返り値1でプログラムが終了します。

テストで検出したバグをコンパイラ警告として出力してくれるHaxeライブラリ、NanoTestを公開しました

FlashDevelopcapture-20140201-150704


GUIを使ってテストのデバッグがしたい

Haxeには標準で、haxe.unitというテスト用のライブラリがあります。とてもシンプルなライブラリです。低い敷居でテストを導入できて良いのですが、もの足りないなーと思うところもあります。

何がもの足りないのか?

足りないのはGUIです。グラフィカルなユーザーインタフェースです。黒い画面上に表示されたメッセージを眺めて、ファイルを探して、失敗した行を探して、1つ1つバグを潰していくのは、なんとも骨が折れます。

「ずらっとならんだメッセージをクリックしたら、テスト失敗した行にすぐ飛べる!」とか「失敗行を赤線で示してくれる!」とかそういうGUIがあればもっと楽に、簡単にテストができるわけです。コンパイルエラーでは、エラー行へジャンプする機能も、エラー行をハイライトしてくれる機能もIDEについています。コンパイルエラーと同じように、テストの失敗も表示してくれればデバッグもはかどるわけです。

うれしいことにHaxeのコンパイラはそれを実現できる機能があります。Haxeには、コンパイル前にコードを実行して、その際にコンパイルエラーやコンパイラ警告を出力することができる機能があります。

それが、これ、Compiler Configuration with Macros。いわゆるマクロです。コンパイル中ではなく、コンパイル前に動作するマクロなので、自分はコンパイル前マクロと呼んでいます。今回は、このコンパイル前マクロでつかうテスト用のライブラリNanoTestを作りました。


NanoTest(ナノテスト)

ソースコード:https://github.com/shohei909/NanoTest
ライセンス:MIT Lisence

NanoTest(ナノテスト)の特徴は以下のとおり

  • テストの書き方はhaxe.unitとほぼ同じ。すでにhaxe.unit用に記述したコードがあるならばTestRunnerをNanoTestRunnerに、TestCaseを、NanoTestCaseに置き換えれば、同じように動作します。シンプルなので学習コストも低いです。
  • NanoTestをマクロとして実行することで、テストの失敗をコンパイラ警告やコンパイルエラーとして出力することができます。FlashDevelopだろうとIntelliJ IDEAだろうと、いつもと同じUIでテスト失敗が警告されるので、快適にデバッグをすることができます。

インストール

haxelibをつかってインストールすることが可能です。コンソール上で以下のコマンドを打ち込んでください。

haxelib install nanotest

使い方

基本的な、テストの書き方はHaxeマニュアルのWriting Unit Testsとほぼ同じです。

NanoTestの場合、以下のようなファイルをsample/TestSample.hxとして保存します。

package sample;
import shohei909.nanotest.NanoTestRunner;
import shohei909.nanotest.NanoTestCase;
 
class TestSample {   
    static function main(){
        var r = new NanoTestRunner();
        r.add(new TestFoo());
        r.run();
    }
}
 
class SampleCase extends NanoTestCase {
    public function testBasic(){
        assertEquals( "A", "A" );
    }   
}

これを、以下のようなhxmlファイルでコンパイルして実行すれば、haxe.unitと同じようにテストが出来ます。

-neko testSample.n 
-main TestSample
-lib nanotest

上記のテストをマクロとして実行したい場合、hxmlを以下のように書き変えます。

--no-output
--macro sample.TestSample.main()
-lib nanotest

“–macro”で指定した関数が、コンパイル前に実行させれて、そこでテストの失敗があればコンパイラ警告としてそれが出力されます。

このhxmlを使ってIDEがコンパイルするように設定すれば、IDEがコンパイラ警告としてテスト失敗を出力してくれるので、コンパイルエラーをとりのぞくのと同じようにテストのデバッグを行うことが出来ます。

※ FlashDevelopでhxmlを利用する方法は、ActionScript入門Wiki – FlashDevelopでHaxeプロジェクトを作成するで詳しく紹介されています。

より高度な使い方

テスト失敗を、コンパイル警告ではなくコンパイルエラーとして出力する

NanoTestRunnerのコンストラクタの引数に、NanoTestRunner.error関数を渡しましょう。

こんな風に

var r = new NanoTestRunner(NanoTestRunner.error);

コンパイル警告ではなくコンパイルエラーを出力する設定は、nightlyビルドのような自動ビルドをしたい場合に役に立ちます。つまり、テスト失敗時にコンパイルエラーになるようにしとけば、まちがってユーザーにバグを含むビルドを配布することを防げるので、安心して自動ビルドができます。

unit.haxe.TestCaseには無い関数について

NanoTestCaseは、unit.haxe.TestCaseと同じように使用することが出来ますがunit.haxe.TestCaseにはない追加の関数をもっています。

assertThrows( func:Void->Void, ?isSuccess:Dynamic->Bool, ?p : PosInfos )
funcを実行した場合に例外が投げられることをテストします。さらにisSuccessを設定した場合には、投げられた例外が正しいかどうかを、catchしたオブジェクトをisSuccess関数に渡してテストします。
globalSetup()
テストケースのクラスが複数持っているテスト用のメソッド(“test”ではじまるメソッド)の内、一番最初のメソッドが呼び出される前に呼び出されるメソッド。overrideして使う。
globalTearDown()
テストケースのクラスが複数持っているテスト用のメソッドの内、一番最後のメソッドが呼び出された後に呼び出されるメソッド。overrideして使う。
success( ?p:PosInfos )
テスト成功を出力します。
fail( message:String, ?p:PosInfos )
テスト失敗を出力します。
error( d:Dynamic )
テスト中に発生した例外を出力します。

またTestCaseとは異なり、assertEquals関数がEnumの比較をサポートしています。

注意点

マクロを使ったテストは、あくまでNekoに出力した場合のプログラムをテストしていることになります。FlashやJavaScriptなどその他のターゲットを使用している場合、そのターゲットに依存したコードについてはマクロではテスト出来ません。NanoTestでは、haxe.unit同様、実際にそのターゲットに出力した上で実行してテストするのも可能なので、ターゲット依存のコードについてはマクロを使わずにテストするようにしてください。

更新情報

v0.1.2(2014/02/02 17:29)
swf上で動作させた場合に上手く動かないバグがあったので修正。
v0.1.3(2014/02/02 22:05)
run関数を呼び出す前にassertできるようになりました。これにより非同期のテストが、書きやすくなりました。

Haxe3の新機能まとめ

本日2013/05/25、プログラミング言語Haxeのバージョン3が正式にリリースされました!
Haxe3のダウンロード:http://haxe.org/download

ということで、Haxe3がこれまでのHaxe2.10とどうが違うのかを徹底的に紹介していきます!


新機能

Java, C#ターゲットのサポート

これまで、Flash(AIRも)、JavaScript、C++、PHP、Nekoへの出力がサポートされていましたが、これらに新たにJava、C#が追加されました。

‘シングルクオテーション内での、変数展開

Haxe3から、「’」と「”」の扱いが変わります。

var x = 10;
trace( 'x is $x' )	//x is 10
trace( "x is $x" )	//x is $x

これまでのString.Format()関数の代替です。
「’」内で$変数名や${式}と記述した場合に、その変数や式が文字列内に組み込まれます。

新しいハッシュテーブルのクラス(Map)

クロスプラットフォームの辞書(ハッシュテーブル)のクラスが文法レベルでサポートされました。

var price:Map <String , Int > = [
	"カレー"        => 420,
	"カツカレー"    => 550,
	"ラーメン"      => 380
];

price.set("カレー",400);
trace( price.get( "カレー" ) ); //400

さらに、配列の演算子を使って値を参照することができます。

price["カレー"] += 30;

さらに、enumをカギに使うことも可能です。

配列内包表記

for文や、while文を使った配列の初期化がサポートされました。

//0~20の偶数の配列!
var evenArray = [for (i in 0...21) if (i & 1 == 0) i];

パターンマッチング

switch文を使ったパターンマッチングがサポートされました。
多機能なので、詳しくはhttp://haxe.org/manual/pattern_matchingを見てください。

パッケージをまとめてインポート

import my.pack.*;

のような書き方が出来るようになりました。

bind関数

callback構文の代替です。
より簡単に、関数の引数を束縛することができます。

function add(a:Int, b:Int) { trace( '$a,$b' ); }
var func1 = add.bind(3);
var func2 = add.bind(_, 4);

func1( 2 ); //add(3,2)と同じ
func2( 0 ); //add(0,4)と同じ

抽象型(abstract)

Haxe3では、新たに抽象型(abstract)が加わりました。
これは、JavaやC#の抽象クラスや抽象メソッドとは大きく異なるものです。

Haxeの抽象型は実行時には他の型として実行される型で、変換元となる型、変換先となる型、変換方法などを定義して使用します。
カスタム配列アクセス、演算子オーバーロードなどが使用出来ます。

NekoのIntが31bit整数から32bit整数に

Haxe2.10までのNeko出力では、

var argb = 0x66FFFFFF;

のように、31bitであらわせない数値が含まれているとコンパイルエラーになってました。
Haxe3では上記のコードは正常に動作します。

出力されるコードが綺麗に

Haxeには、未使用の関数や変数を自動で取り除くデッドコード削除という機能があります。
Haxe3では、この強さを3段階から選択することが出来ます。

–dce no: デッドコード削除を行わない(Haxe2.10のデフォルト)
–dce std: 自分で書いたコードから呼び出していない、標準ライブラリ、外部ライブラリのコードを削除。(新機能、Haxe3のデフォルト)
–dce full: main関数から呼び出していないコードについて削除を行う。(Haxe2.10で–dead-code-eliminationを指定した場合と同じ

デフォルトでのデッドコード削除は強化されたため、基本的にHaxe3から出力されるコードは綺麗になります。

型パラメータ(ジェネリクス)の強化。

構造的部分型による、型パラメータの束縛。

static function test < T: {function clone():T} >( a:T ) {}

enum(列挙型)のコンストラクタに対する、型パラメータの設定。

enum EnumTest{
	 HOGE < T >(t:T);
}

など、型パラメータの機能が強化されました。

マクロ機能の改善

詳しくはhttp://haxe.org/manual/haxe3/features

その他

  • コンストラクタのインライン化が可能に
  • Flashで、swcをそのまま外部ライブラリとして利用可能に
  • traceに複数の値が指定可能に

新しい標準ライブラリ

標準ライブラリの詳細はhttp://haxe.org/apiに載っています。

クロスプラットフォームのZIP圧縮、解凍

パッケージ: haxe.zip.*;
Flash, JavaScript, C++, PHP, Neko, C#, Javaで動くzipライブラリです。

暗号ライブラリの強化

パッケージ: haxe.crypto.*;
もともとあったMd5,Sha1,BaseCodeに加えて、Crc32,Adler32が使えるようになりました。

データ構造の追加

パッケージ: Map, haxe.ds.*;
いままで標準で、
Array < T >(可変長配列),
List < T >(連結リスト),
Hash < T >(文字列が鍵のハッシュ表)をサポートしていたのですが、

Hashが無くなり、

あらたに、
Map < K, V >(汎用なハッシュ表)、
GenericStack < T >(スタック)、
Vector < T >(固定長配列)、
BalancedTree < T >
が加わりました

これらはすべてクロスプラットフォームです。
VectorはいくつかのターゲットではArrayより速く、すべてのターゲットでArrayと同等以上の速度が出ます。

Enumの補助クラス

パッケージ: haxe.EnumTools;

JavaScriptターゲットがHTML5に対応

パッケージ: js.html.*;

HTML5の機能が標準で利用できるようになりました。
svg,webglなどもサポートされます。

その他

  • Array.map、Array.filter

移動したライブラリ

cpp.Sys Sys
php.Sys Sys
neko.Sys Sys
Lib.print Sys.print
neko.db sys.db
php.db sys.db
cpp.net sys.net
php.net sys.net
neko.net sys.net
cpp.io sys.io
php.io sys.io
neko.io sys.io
neko.zip haxe.zip
cpp.Utf8 haxe.Utf8
php.Utf8 haxe.Utf8
neko.Utf8 haxe.Utf8
haxe.BaseCode haxe.crypto.BaseCode
haxe.SHA haxe.crypto.Sha1
haxe.MD5 haxe.crypto.Md5
js.Lib.window js.Browser.window
js.Lib.document js.Browser.document
IntIter IntIterator
tringTools.isEOF StringTools.isEof
haxe.Stack haxe.CallStack
EReg.customReplace EReg.map

新しいメタデータ

@:to, @:from
抽象型(abstract)で使用します。型の変換方法を定義します。
@:arrayAccess
抽象型(abstract)で使用します。カスタム配列アクセスを定義します。
@:op
抽象型(abstract)で使用します。演算子オーバーロードを定義します。
@:publicFields
クラスのデフォルトの可視性をpublicに変更します。
@:noCompletion
コンパイラの補完機能に反映されないようにします。
@:noUsing
usingした際に反映されないようにします
@:font
Flashのみです。フォントの埋め込みを行います。文字コードの範囲指定も可能です。

新しいコンパイラオプション

-dce [std|full|no]
デッドコード削除の強さを(no,std,full)から選択します。
-swf-lib-extern < file >
externのSWFファイルを指定する
-version
バージョンの確認
–help-defines
有効なコンパイラ定義(-D)のリストを表示
–help-metas
コンパイル時に使用されるメタデータを表示

置き換えられたもの

–dead-code-eliminationが-dce fullに
–js-modernがデフォルトに
これにより、JavaScript全体が(function(){“use strict”; })()に囲まれます。コンパイラオプション-D js-classicでこれを回避できます。さらに詳しくは、http://haxe.org/doc/js/modern
extendsとimplementsを同時に指定するときの書き方が変わりました。
haxe2:

... extends haxe.Template, implements InterfaceSyntax2 { }

haxe3:

... extends haxe.Template implements InterfaceSyntax2 { }
プロパティのgetter,setterの書き方が変わりました
詳しくは、http://haxe.org/manual/haxe3/migration#property-accessors

無くなったもの、出来なくなったもの

jQueryライブラリの自動的な埋め込み
Haxe2以前では、標準ライブラリに含まれるJQueryのライブラリを使った場合に出力されるjsにjQueryのコードが埋め込まれていました。
Haxe3では、このjQueryの埋め込みが無くなりました。
対策: コンパイル時に-D embed-jsのオプションを指定する(2013/07/15加筆 コメントで教えてもらいました) または、jQueryのjsファイルをダウンロードしてきてHaxeのjsより前に埋め込む。
Array < Dynamic > に対する型推論
対策: 明示的に型指定を行う
inline変数を参照型の値で初期化
対策: inline関数を使う
Int32
対策: Intを使う
String.format()
対策: ”(シングルクオテーション)を使う。
callback
対策: bind関数を使う
プロパティでget, setを両方指定した際の、デフォルト変数
対策: @:isVarを明示的に指定する
Hash, IntHashクラス
対策: Mapを使う
haxe.Publicインターフェース
対策: @:publicメタデータを使う
haxe.rtti.Genericインターフェース
対策: @:genericメタデータを使う
コンパイラオプション -as3-native

Haxe3での主な変更点は以上のとおりです!あれが足りない、ここが間違ってるなどあればコメントしていただけると嬉しいです。

2013/05/31_訂正:
@:noCompleteって書いてありましたが、正しくは@:noCompletionでした。
3.0RC出たあたりでwikiが変更されてたのを見逃してました。
http://haxe.org/manual/tips_and_tricks?version=15566

2013/08/21_追記:

C#,Javaの正式サポートって書いたんですが、よくよくいろんなところ見返してみたら、betaから、stableになりましたよっていう正式な発表が見つかれませんでした…

2.09リリース時点では、Haxe3.0でC#,Javaをサポートする予定(http://www.silexlabs.org/133720/the-blog/haxe-3-and-4-plans-for-world-domination/)で、2.10リリース時http://haxe.org/doc/release/2.10にも”their final implementation in Haxe 3.0″って書いてあったので、3.0でC#,Javaが正式サポートだと思ってたのですが、3.0公開後の発表ではそこらへんに触れてるものがなかったです

逆に3.0のJava,C#周りの記述でまだbetaだよ記述も無くて、あたらしいgithubレポジトリにはJavaやC#のターゲットのところにもbetaと書かれてないので、このままこっそりbetaの文字が消えてくような気もします。

とはいえ、もしかしたら、次のリリース以降でbetaじゃなくなりましたよって発表があるかもしれません。

ここらへんちゃんと知ってる人がいたら教えてほしいです。

マージソートで速度比較【C#, C++, Java, PHP vs. Haxe】

 最近はWindowsアプリケーションの制作にC#を使ってるんですが、Haxe/C++ってどうなのよ、C++と比べてどのくらいの速さなのよ?ってことが気になったので速度比較しました。つかったのは、マージソートです。理由は先日のCODE VS 2.0(http://codevs.jp/)で配列操作がボトルネックになってるように見えたからです。

マージソートって何?って人もいると思うのでちょっと紹介。

右上のrandomを押したあと、左上でMargeを選択してみましょう。
このような方法で数字の大小を比較して、順番に並べるのがマージソートです。

速度比較用コード

Haxe

package;

class Main {
    static inline var SIZE = 1000000;
    
	static function main() {
		var start = getTime();
        var arr = new Array<Int>();
		
		//Neko,PHP,Java,C#は初めに領域確保。これをやらないと配列を伸ばすごとに時間がかかる。
		#if (neko || php || java || cs)
		arr[SIZE - 1] = 0;
		#end
		
		for( i in 0...SIZE ){ arr[i] = SIZE - i; }
        margeSort( arr );
        var time:Int = Math.round(getTime() - start);
		trace( time + "ms" );
	}
     
    public static function margeSort( arr:Array<Int> ){
        var l   = arr.length;
        var tmp = new Array<Int>();
		
		#if (neko || php || java || cs)
		tmp[SIZE - 1] = 0;
		#end
		
        var i = 1;
        while( i < l ) {
            var j = 0;
            while( true ){
                var l0 = j + i;
                if( l0 >= l )    break;
                 
                var l1 = l0 + i;
                if( l1 > l ) l1 = l;
                 
                var p0 = j, p1 = l0, val0 = arr[p0], val1 = arr[p1], tp = 0;
                 
                while ( true ){
                    if( val0 <= val1 ){
                        tmp[ tp++ ] = val0;
                        if( ++p0 == l0 ) break;
                        val0 = arr[ p0 ];
                    }else{
                        tmp[ tp++ ] = val1;
                        if( ++p1 == l1 ){
                            var tl = l0 - p0;
                            copyArray( arr, p0, arr, l1 - tl, tl );
                            break;
                        }
                        val1 = arr[ p1 ];
                    }
                }
                copyArray( tmp, 0, arr, j, tp );
                j = l1;
            }
            i <<= 1;
        }
    }
     
    public static inline function copyArray( source:Array<Dynamic>, sourceIndex:Int, target:Array<Dynamic>, targetIndex:Int, length:Int ) {
        var i = length;
        while( --i >= 0 ) target[targetIndex + i] = source[sourceIndex + i];
    }
	
	public static inline function getTime() {
		#if (flash || js || cs)
		return Date.now().getTime();
		#else
		return Sys.time() * 1000;
		#end
	}
}

 関数の頭にinlineと付けるだけでインライン展開できるのがポイント。Haxeから出力されたC++では、copyArrayの関数自体は消えてなくなり、中身のループが呼び出し位置に展開された形になります。

 あと、配列の長さが指定できないのが特徴で、PHP, Neko, C#, Javaに出力する際は注意が必要になります。これらの言語を選択した場合、配列の長さを伸びるごとに配列の長さに比例した時間が取られてしまいます。あらかじめ配列を伸ばしておいてから数値を代入しましょう。

 せっかくなので、JavaScript, AIR, NekoVM, PHP, C++, C#, Javaに出力しました。上のコードでこれらすべての言語に対応してます。

C#

using System;
using System.Diagnostics;

namespace SpeedTest{
	class Program{
		public const int SIZE = 1000000;
		
		public static void Main(string[] args){
			var sw = new Stopwatch();
			sw.Start();
			
			int[] arr = new int[ SIZE ];
			for( int i = 0; i < SIZE; i++ ){
				arr[i] = SIZE - i;
			}
			MargeSort( arr );
			
			Console.WriteLine( sw.ElapsedMilliseconds + "ms" );
			Console.ReadKey(true);
		}
		
		//小さい順にソート
		public static void MargeSort( int[] arr ){
			int 	l 	= arr.Length;
			int[] 	tmp	= new int[l];
			
			for( int i = 1; i < l; i <<= 1 ){
				for( int j = 0, l0, l1; ; j = l1 ){
					l0 = j + i;
					if( l0 >= l )	break;
					
					l1 = l0 + i;
					if( l1 > l )	l1 = l;
					
					int p0 = j, p1 = l0, val0 = arr[p0], val1 = arr[p1], tp = 0;
					
					while( true ){
						if( val0 <= val1 ){
							tmp[ tp++ ] = val0;
							if( ++p0 == l0 ) break;
							val0 = arr[ p0 ];
						}else{
							tmp[ tp++ ] = val1;
							if( ++p1 == l1 ){
								int tl = l0 - p0;
								var targetIndex = l1 - tl;
								var i1 = tl;
								while(--i1 >= 0) {
									arr[targetIndex + i1] = arr[p0 + i1];
								}
								
								break;
							}
							val1 = arr[ p1 ];
						}
					}
					
					{
						var i1 = tp;
						while(--i1 >= 0) {
							arr[j + i1] = tmp[i1];
						}
					}
				}
			}
		}
	}
}

 copyArrayの部分はループに変更しています。普段ならそんなめんどくさいことやらないでArray.Copy()を使ってますが、Array.Copy()使うと50msくらい遅くなります。

C++

#include <iostream>
#include <time.h>

using namespace std;
int SIZE = 1000000;

void margeSort( int* arr, int l ){
    int* 	tmp	= new int[l];

    for( int i = 1; i < l; i <<= 1 ){
        for( int j = 0, l0, l1; ; j = l1 ){
            l0 = j + i;
            if( l0 >= l )	break;

            l1 = l0 + i;
            if( l1 > l )	l1 = l;

            int p0 = j, p1 = l0, val0 = arr[p0], val1 = arr[p1], tp = 0;

            while( true ){
                if( val0 <= val1 ){
                    tmp[ tp++ ] = val0;
                    if( ++p0 == l0 ) break;
                    val0 = arr[ p0 ];
                }else{
                    tmp[ tp++ ] = val1;
                    if( ++p1 == l1 ){
                        int tl = l0 - p0;
                        int targetIndex = l1 - tl;
                        int i1 = tl;
                        while(--i1 >= 0) {
                            arr[targetIndex + i1] = arr[p0 + i1];
                        }
                        break;
                    }
                    val1 = arr[ p1 ];
                }
            }
            {
                int i1 = tp;
                while(--i1 >= 0) {
                    arr[j + i1] = tmp[i1];
                }
            }
        }
    }
}

int main() {
    int start = clock();
    
    int* arr = new int[SIZE];
    for( int i = 0; i < SIZE; i++ ){ arr[i] = SIZE - i; }
    margeSort( arr, SIZE );
    
    std::cout << (clock() - start) << "ms\n";
    return 0;
}

 C++は苦手です。やりようによってはまだまだ高速化できるのかもしれませんが、わからないのでできません。C#のコードをなるべくそのまま移植しました。

Java

public class Main {
    public static final int SIZE = 1000000;
	
    public static void main(String[] args) {
        // TODO code application logic here
        long start = System.currentTimeMillis();
        int[] arr = new int[ SIZE ];
        for( int i = 0; i < SIZE; i++ ){
            arr[i] = SIZE - i;
        }
        margeSort( arr );
        
        System.out.println( (System.currentTimeMillis() - start) + "ms" );
    }

    //小さい順にソート
    public static void margeSort( int[] arr ){
        int 	l 	= arr.length;
        int[] 	tmp	= new int[l];

        for( int i = 1; i < l; i <<= 1 ){
            for( int j = 0, l0, l1; ; j = l1 ){
                l0 = j + i;
                if( l0 >= l )	break;

                l1 = l0 + i;
                if( l1 > l )	l1 = l;

                int p0 = j, p1 = l0, val0 = arr[p0], val1 = arr[p1], tp = 0;

                while( true ){
                    if( val0 <= val1 ){
                        tmp[ tp++ ] = val0;
                        if( ++p0 == l0 ) break;
                        val0 = arr[ p0 ];
                    }else{
                        tmp[ tp++ ] = val1;
                        if( ++p1 == l1 ){
                            int tl = l0 - p0;
                            System.arraycopy( arr, p0, arr, l1 - tl, tl );
                            break;
                        }
                        val1 = arr[ p1 ];
                    }
                }
                System.arraycopy( tmp, 0, arr, j, tp );
            }
        }
    }
}

 C#のコードをほぼそのまま移植しました。こうやって見るとほんとC#にそっくりです。配列のコピーはループよりSystem.arraycopy()のが高速でした。

PHP

<?php
function margeSort($arr) {
	$l = count($arr);
	$tmp = array();
	for ($i = 1; $i < $l; $i <<= 1) {
		$j = 0;
		while(true) {
			$l0 = $j + $i;
			if($l0 >= $l) { break; }
			$l1 = $l0 + $i;
			if($l1 > $l) { $l1 = $l; }
			$p0 = $j; $p1 = $l0; $val0 = $arr[$p0]; $val1 = $arr[$p1]; $tp = 0;
			while(true) {
				if($val0 <= $val1) {
					$tmp[$tp++] = $val0;
					if(++$p0 === $l0) {
						break;
					}
					$val0 = $arr[$p0];
				} else {
					$tmp[$tp++] = $val1;
					if(++$p1 === $l1) {
						$tl = $l0 - $p0;
						$targetIndex = $l1 - $tl;
						$i1 = $tl;
						while(--$i1 >= 0) {
							$arr[$targetIndex + $i1] = $arr[$p0 + $i1];
						}
						break;
					}
					$val1 = $arr[$p1];
				}
			}
			{
				$i1 = $tp;
				while(--$i1 >= 0) {	$arr[$j + $i1] = $tmp[$i1]; }
			}
			$j = $l1;
		}
	}
}

define( "SIZE", 1000000 );
$start = microtime(true) * 1000;
$arr = array();
$_g = 0;
while($_g < SIZE) {
	$i = $_g++;
	$arr[$i] = SIZE - $i;
}
margeSort($arr);
$time = floor(microtime(true) * 1000 - $start);
echo $time."ms";
?>

Haxe/PHPがあまりにもかわいそうな結果だったので、PHPも書きました。こっちのがいくらかマシです。

結果

速い順です。

言語 時間[ms] C++との比較
C++ 37
C# 72 1.95倍
Java 74 2倍
Haxe/C++ 119 3.22倍
Haxe/JS [Node.js] 274 7.41倍
Haxe/C# 501 13.5倍
Haxe/AIR 744 24倍
Haxe/Java 1155 31.2倍
Haxe/Neko 4282 116倍
PHP 19853 537倍
Haxe/PHP 132853 3590倍

 やはりC++が高速。続いて、C#,Javaがほぼ同じ。ちょっと遅れてHaxe/C++。

 Haxe/C++とC#で結構な差があるように見えますが、先述のArray.Copy()でひっくり返りかねない差なので、C#,Java,Haxe/C++は同じくらいのレベルと考えていいと思います。個人的にC++はあまり使いたくないので、速さが必要なプログラムはこの3つのどれかで作ることになるでしょうか。

 Haxeはマージソートくらいだと他言語に簡単に移植できるのがいいですね。ついでなので他の言語への移植もしてみましたが、Haxe/JSはかなり健闘しました。Node.jsって遅いイメージがあったんですが、かなり高速らしいです。

 Haxe/AIRは、まあ普通という感じです。AS3で書いてもこのへんになる気がします。
 
 Haxe/C#、Haxe/Javaはベータ版ということもありまだ洗練されていないように見えます。これからに期待。
 
 Haxe/Nekoも遅いですね。Node.jsの方がはるかに速いです。
 
 PHPが遅いのは当然の結果ですね。C++の500倍遅かろうとPHPは良い言語ですし…。そもそも、PHPで速度が必要なプログラム書くこと自体が間違ってますし…。HTML生成できて、JSON出力できて、データベース操作できて、exec()でコマンド実行出来れば、それだけでPHPは価値があると思いますね。

 Haxe/PHPはさらに遅いです。PHPはただでさえ遅いのに配列に独自のクラスをつかってるので、無駄な関数呼び出しが発生してるようでした。Haxe/PHPはPHPの良い部分も死んでる(HTMLに直接書き込めない、1ページに1プロジェクト必要など)気がするので、自分が使う機会はなさそうです。

 ともあれ、Haxe/C++, Haxe/JS, Haxe/Flash(AIR)はかなり実用的だと思います。これだけできれば、モバイルでも、ブラウザでも、サーバーサイドでも、PCネイティブでも使えるので、プラットフォームと使える言語がマッチしなくて困ることはほぼ無さそうです。

 Haxeに興味を持った人はとりあえず公式サイト(http://haxe.org/?lang=jp)にどうぞ。

使った環境について

Windows 7 Home Premium 64-bit
Intel(R) Core(TM) i5-2430M
メモリ: 4G

Haxe 2.10, cygwin gcc 3.4.4(C++), .NET Framework 4.5(C#) 4.0(Haxe/C#), Visual Studio 2010(Haxe/C++), node.js 0.8.11, AIR 3.4, neko vm 1.8.2, php 5.3.10