はじめに
x10はある程度は最適化してくれるものの、やはり性能はチューニングしたC,C++にはおよびません。そこでプログラムのホットスポットの関数のみC,C++で書いて、その関数をx10のコードから呼ぶことができます。
またC,C++の方がコミュニティが大きい分、便利なライブラリが多く、そのライブラリをそのまま利用できると嬉しいことがあります。
ここではnative版(C++版)x10からC++の関数を呼ぶ方法について紹介します。
参考:
- http://x10-lang.org/documentation/practical-x10-programming/native-code-integration.html
- http://x10.sourceforge.net/documentation/languagespec/x10-latest.pdf の18章
C++の関数を呼ぶ
コードサンプル
MyCpp.hpp, MyCpp.cpp で宣言・定義された関数を呼ぶサンプル
import x10.io.Console;
import x10.compiler.Native;
import x10.compiler.NativeCPPInclude;
import x10.compiler.NativeCPPCompilationUnit;
@NativeCPPInclude("MyCpp.hpp")
@NativeCPPCompilationUnit("MyCpp.cpp")
public class NativeCppTest {
@Native("c++", "my_double(#1);")
native static def double(n:Int): Int;
public static def main( args: Rail[String] ) {
@Native("c++", "foo();") {}
val n = 30n;
val n2 = double(n);
Console.OUT.println("n2: " + n2);
}
}
void foo();
int my_double(int n);
#include <iostream>
#include "MyCpp.hpp"
void foo() {
std::cout << "hello from foo" << std::endl;
}
int my_double(int n) {
return 2 * n;
}
関数を呼ぶときは2通りの書き方があります。
Native Static Method
C++関数をx10のstaticメソッドにマッピングする。
@Native("c++", "my_double(#1)")
native static def double(n:Int): Int;
- メソッドの引数は
#1
,#2
に埋め込まれてC++の関数が呼ばれます。 - native修飾子をつけると、x10での関数定義(native関数が見つからない場合に呼ばれる)ができなくなります。
- つまりこの例ではmanaged版(Java版)のコンパイルはできません。
- 少なくともprimitive型は返り値にできます。ただし型推論はできないので、返り値の型(この場合はInt)とパラメータの型を明示する必要があります。
- 外部のC++ファイル
@NativeCPPInclude
でヘッダファイル、@NativeCPPCompilationUnit
でcppファイルをそれぞれ指定します。
Native Blocks
@Native("c++", "foo();") {}
- このように書いてx10のメソッドを作らずに呼ぶこともできます。
{}
は必須です。x10のstatementを@Nativeの中身で置き換えています。 - もし上記のコードがjavaにcompileされた場合、空のstatementが実行されるはずです。(つまり何も実行されない)
- 引数を渡す方法はない(?)が、static変数を埋め込むことができます。
コンパイル方法
cpp,hpp,x10のファイルをすべて同一のディレクトリに置いている場合は、単純に以下のコマンドでコンパイルできます。
$ x10c++ NativeCppTest.x10
外部のライブラリをリンクする場合、インクルードパスとライブラリパス、リンクオプションを次のように指定します。
x10c++ Test.x10 -post '# # -I /usr/local/blas # -L /usr/local/blas -lblas'
最初の #
はコンパイラの実行コマンド(g++とか)、二つ目はC++のファイルのリストとCXXFLAGS、3つ目はリンカオプションが埋め込まれ、埋め込まれた文字列が実行されます。
Stringを引数に渡す方法
primitive型の変数はC++の適切な型の変数にマッピングされます。
しかし文字列の場合は一工夫必要です。
例えば、文字列を引数として渡してsystemコマンドを実行するメソッドを考えましょう。
次のように書くことができます。
@Native("c++", "system( (#1)->c_str() );")
native static def system(cmd:String):Int;
x10のStringをC++に変換すると x10::lang::String というC++のクラスに変換され、そのクラスの中に c_str()
というメソッドが定義されています。C++の std::string#c_str()
と同じくchar*を返します。
配列を引数に渡す方法
Railの0番目の要素のアドレスをCに渡す。
import x10.compiler.*;
@NativeCPPInclude("foo.h")
@NativeCPPCompilationUnit("foo.c")
public class NativeTest {
@Native("c++", "foo((double*)(&((#1)->raw[0])), (int*)(&((#2)->raw[0])))")
native static def foo(tmp:Rail[double],tmp2:Rail[int]):Int;
public static def main(args:Rail[String]) {
t:Rail[double] = new Rail[double](3);
t2:Rail[int] = new Rail[int](3);
foo(t,t2);
for(d in t) Console.OUT.println(d);
for(i in t2) Console.OUT.println(i);
}
}
int foo(double * tmp, int * tmp2) {
tmp[0]=10.0;
tmp[1]=11.0;
tmp2[2]=21;
}
わかっていないこと
- メモリ解放はどのタイミングで?
- GCは効かない?たぶんC++側で明示的に解放する必要があると思うが、よくわかっていません。
- C++のクラスをX10から参照することができる?
- X10のクラスは上のStringの例のようにC++のクラスにマップされます。
- ユーザー定義型の場合はビルド時に生成されるC++のコードを見れば、どのようにC++のクラスにマップされたかわかります。
- しかし、C++のオブジェクトを指す変数をx10側で参照することができるかは不明です。
- X10のクラスは上のStringの例のようにC++のクラスにマップされます。