NArrayに多次元配列でアクセスする方法

仕事でRuby NArrayライブラリを使って、配列のアクセスを高速化していたのですが、
どうしてもLoopを回す必要があったので、そこをC化しています。


http://d.hatena.ne.jp/yukichanko/20091026/1256573572


NArrayは多次元配列を定義しても、実際には全て1次元配列に展開してしまうので、
Cからアクセスするときも、下記のようなマクロを作り、1次元配列でアクセスしていました。

#define ARY2(var, i, j, nx, val) var[(j) * (nx) + (i)] = (val)
#define ARY3(var, i, j, k, nx, ny, val) \
	var[(k)*(ny)*(nx) + (j)*(nx) + (i)] = (val)


このやり方でもよかったのですが、C側のコードをNArray用にカスタマイズする必要があるので、いまいちうまくありません。


ということで、ラッパを一層追加することにしました。


注意しないといけないのは、NArrayのライブラリは、

a = NArray.float(5, 2)

という定義をしたときに、Xが内側、Yが外側の配列になる、ということ。
図で書くと、
□□□□□■■■■■
という構造になります。


Cの配列だと、イメージ映像は、

double a[5][2];

□■□■□■□■□■
ですよね。


このメモリ配置のみが若干ネックです。ということで、

NArray.float(x, y, z)

と宣言した配列は、

a[z][y][x];

として受けてやる必要があります。


以上のことを踏まえると、ラッパ関数は下記のようになります。

// メモリ確保
double** allocate_2d_array(int nx, int ny)
{
	double** a;
	int y;

	a = (double**)malloc(sizeof(double*) * ny);

	for(y = 0; y < ny; y++) {
		a[y] = (double*)malloc(sizeof(double) * nx);
	}

	return a;
}

// メモリフリー
void free_2d_array(double** a, int nx, int ny)
{
	int y;

	for(y = 0; y < ny; y++) {
		//free(a[y]);    // NArrayからアクセスするので、freeしてはいけない。
	}
	free(a);
}

// Cの多次元配列とNArrayの1次元配列のリンク
void link_2d_data(double* src, double** targ, int nx, int ny)
{
	int y;

	for(y = 0; y < ny; y++) {
		targ[y] = &(src[y*nx]);
	}
}

// 多次元配列での配列操作
void array_2d_init(double** array, int nx, int ny)
{
	int x, y;

	for (y = 0; y < ny; y++) {
		for (x = 0; x < nx; x++) {
			array[y][x] = y*10+x;
		}
	}
}

// ここからピュアC
void rb2c_array_2d_init(double* array, int nx, int ny)
{
	double** a;

	a = allocate_2d_array(nx, ny);   // メモリ領域の確保
	link_2d_data(array, a, nx, ny);   // Cの多次元配列とNArrayデータのリンク
	array_2d_init(a, nx, ny);              // Cの多次元配列の操作
	free_2d_array(a, nx, ny);             // メモリ領域の解放
}

// ラッパ関数
void wrap_array_2d_init(VALUE self, VALUE na, VALUE nx, VALUE ny)
{
	//VALUE na2;
	struct NARRAY *n_na;

	//na2 = na_cast_object(na, NA_DFLOAT);
	GetNArray(na_cast_object(na, NA_DFLOAT), n_na);
	rb2c_array_2d_init((double*)n_na->ptr, NUM2INT(nx), NUM2INT(ny));
}

// Rubyへのメソッド登録
void Init_testnarray()
{
	VALUE module;

	rb_require("narray");
	module = rb_define_module("TestNArray");
	rb_define_module_function(module, "array_2d_init", wrap_array_2d_init, 3);
}


a[z][y][x]としないといけないのが、ダサいのですが、とりあえず、Cのコードを使いまわせるようにはなります。