前回の記事
前回は、Numo::NArray のリポジトリ全体を観察して、プロジェクトのほとんどがC言語で実装されていることを見てきました。今回はもう一歩進んで、extディレクトリの構造を観察していこうと思います。
ext ディレクトリの構成を見る
今回は Numo::NArray の中心となるC拡張のコードが入ったextディレクトリをざっくりと観察していきます。
# 2階層までの深さで、ファイル優先でディレクトリの構造を表示する
tree -L 2 --filesfirst
.
├── SFMT-params.h
├── SFMT-params19937.h
├── SFMT.c
├── SFMT.h
├── array.c
├── data.c
├── depend.erb
├── extconf.rb
├── index.c
├── kwargs.c
├── math.c
├── narray.c
├── narray.def
├── ndloop.c
├── rand.c
├── step.c
├── struct.c
├── gen
│ ├── cogen.rb
│ ├── erbln.rb
│ ├── erbpp2.rb
│ ├── narray_def.rb
│ ├── spec.rb
│ ├── def
│ ├── tmpl
│ └── tmpl_bit
└── numo
├── compat.h
├── intern.h
├── narray.h
├── ndloop.h
├── template.h
└── types
7 directories, 27 files
-
階層のないのところに重要そうなC言語のソースファイルがいくつか置いてあります。
-
numo というディレクトリがあり、そこに、narray.h などの重要そうなヘッダーファイルが梱包されています。
-
gen
というディレクトリにRubyのerbテンプレートを使うタイプのCのコードが置いてあります。
まず、最初にSFMTというファイルが4つありますが、これはそれほど重要ではありません。
SFMT
これは乱数生成のライブラリです。
SFMTは(SIMD oriented Fast Mersenne Twister)という擬似乱数生成器のコードだそうです。C言語では、RubyのGemとは異なり、ライブラリのソースコードは直接コピーして、ソースコードの中に配置することがよくあります。NArrayはこのSFMTをライブラリとして使っていると思われます。ChatGPTにこのSFMTの使い方についてのサンプルコードを作成してもらいました。
#include <stdio.h>
#include "SFMT.h" // SFMTライブラリのヘッダーをインクルード
int main() {
// 乱数のシードを定義
uint32_t seed = 1234;
// SFMTをシードを使って初期化
init_gen_rand(seed);
// 乱数をいくつ生成するかを定義
int num_random_numbers = 10;
// 乱数を生成して出力
printf("生成された32ビットの擬似乱数:\n");
for (int i = 0; i < num_random_numbers; i++) {
uint32_t rand_num = gen_rand32(); // 32ビットの乱数を生成
printf("%" PRIu32 "\n", rand_num); // 生成した数を表示
}
// 数値の範囲を[0, 1)とした乱数を生成
printf("生成された[0,1)の実数乱数:\n");
for (int i = 0; i < num_random_numbers; i++) {
double rand_real = genrand_real2(); // [0,1)の実数乱数を生成
printf("%f\n", rand_real); // 生成した実数を表示
}
return 0;
}
extconf.rb と depend.erb
extconf.rb は、RubyのC拡張にはかならずといっていいほど添付されているRubyのスクリプトで、Makefileを生成します。depend.erb は erbテンプレートですが、依存関係ファイルというのでMakefileで利用されるらしい。
通常のC拡張とはことなり、Numo::NArrayでは、erbテンプレートでCを書くという特殊な技法を行っています。そのため、そのままでは完全なCのソースコードを見ることができません。
この extconf.rb
を実行して、makeを実行するとテンプレートファイルからC言語の完全なソースコードが生成されます。
ruby extconf.rb
make
実行すると、こんな感じでC言語のソースファイルが生成されると思います。これらが、最終的にNArrayのコンパイルに必要なソースファイルとなります。(tokeiにはコードの行数をcsvで出力する機能がついていなかったので、clocでコードの行数を計測し、エクセルを使って行数を簡易的に表示してみました。
このようにしてみると、narray.c
などの重要なコードの方が行数が少なく、それぞれの型についての処理を書いたコードの方が行数が多いのがわかると思います。
ソースコードの最後の部分を読むことが大事だという話
さて、ここで、RubyのC拡張を見る上で重要なコツがあります。
- ファイルを上から順番に読まない
- 一番下まで画面をスクロールしてRubyとCの世界を把握する
私は冗談抜きで、これがRuby初心者がC拡張の世界に入る上での最大のハードルだと考えています。それをQiita記事にしたこともあります。
rb_define に注目して見よう
rb_define_method に注目すると、ファイルでどのモジュールやメソッドが定義されているのか一目瞭然で理解することができます。この考え方で、それぞれのファイルでどのようなメソッドが定義されているのか、一覧にしてみてみましょう。
これは grep で rb_define
を行うことで簡単に実現できます。
ls *c | grep -v t_ | xargs -I{} -L1 -t grep rb_define {}
array.c
- 配列全体に関する操作を提供するメソッドが実装されている。
-
array_shape
、array_type
:配列の形状や型情報を取得。 -
new_like
:他の配列を基に新しい配列を生成。 -
[]
:配列アクセスのエントリーポイント。
-
rb_define_singleton_method(cNArray, "array_shape", na_s_array_shape, 1);
rb_define_singleton_method(cNArray, "array_type", na_s_array_type, 1);
rb_define_singleton_method(cNArray, "new_like", na_s_new_like, 1);
rb_define_singleton_method(cNArray, "[]", nary_s_bracket, -2);
data.c
- 配列データ操作や変形に関するメソッドが実装されている。
-
copy
、flatten
、reshape
、transpose
:配列のデータ変換。 -
swapaxes
:軸の交換。 -
diagonal
:対角要素の抽出。 - バイトオーダー変換:
swap_byte
、to_network
、to_host
。 - 型に関連したエイリアス定義(例:
little_endian?
、network_order?
)。
-
rb_define_method(cNArray, "copy", na_copy, 0); // deprecated
rb_define_method(cNArray, "flatten", na_flatten, 0);
rb_define_method(cNArray, "swapaxes", na_swapaxes, 2);
rb_define_method(cNArray, "transpose", na_transpose, -1);
rb_define_method(cNArray, "reshape", na_reshape,-1);
rb_define_method(cNArray, "reshape!", na_reshape_bang,-1);
rb_define_alias(cNArray, "shape=","reshape!");
rb_define_method(cNArray, "diagonal", na_diagonal,-1);
rb_define_method(cNArray, "swap_byte", nary_swap_byte, 0);
rb_define_alias(cNArray, "hton", "swap_byte");
rb_define_alias(cNArray, "network_order?", "byte_swapped?");
rb_define_alias(cNArray, "little_endian?", "host_order?");
rb_define_alias(cNArray, "vacs_order?", "host_order?");
rb_define_method(cNArray, "to_network", nary_to_network, 0);
rb_define_method(cNArray, "to_vacs", nary_to_vacs, 0);
rb_define_method(cNArray, "to_host", nary_to_host, 0);
rb_define_method(cNArray, "to_swapped", nary_to_swapped, 0);
//rb_define_method(cNArray, "dot", numo_na_dot, 1);
index.c
- 配列の要素アクセスやスライスに関連したメソッドが実装されている。
-
slice
:部分配列の抽出。 -
[]
、[]=
:要素の取得と設定。 -
at
:特定の位置の要素取得。
-
rb_define_method(cNArray, "slice", na_slice, -1);
rb_define_method(cNArray, "[]", na_aref, -1);
rb_define_method(cNArray, "[]=", na_aset, -1);
rb_define_method(cNArray, "at", na_at, -1);
kwargs.c
- 該当なし
- キーワード引数を扱う補助的な処理
math.c
- 数値演算に特化したメソッドを
NMath
モジュールとして提供。-
method_missing
:動的に数学関数をディスパッチ。
-
numo_mNMath = rb_define_module_under(mNumo, "NMath");
rb_define_singleton_method(numo_mNMath, "method_missing", nary_math_method_missing, -1);
rb_define_const(numo_mNMath, "DISPATCH", hCast);
narray.c
-
NArray
クラスの基幹機能を実装。- クラスの定義やエラーハンドリング関連の例外クラスを定義。
- 配列の初期化、属性取得:
initialize
、size
、shape
、ndim
。 - 基本的な操作:
zeros
、ones
、eye
、linspace
。 - メモリ管理やエンディアン変換:
free
、byte_swapped?
。 - 内部デバッグやプロファイリング関連。
mNumo = rb_define_module("Numo");
cNArray = rb_define_class_under(mNumo, "NArray", rb_cObject);
rb_define_const(cNArray, "VERSION", rb_str_new2(NARRAY_VERSION));
nary_eCastError = rb_define_class_under(cNArray, "CastError", rb_eStandardError);
nary_eShapeError = rb_define_class_under(cNArray, "ShapeError", rb_eStandardError);
nary_eOperationError = rb_define_class_under(cNArray, "OperationError", rb_eStandardError);
nary_eDimensionError = rb_define_class_under(cNArray, "DimensionError", rb_eStandardError);
nary_eValueError = rb_define_class_under(cNArray, "ValueError", rb_eStandardError);
rb_define_singleton_method(cNArray, "debug=", na_debug_set, 1);
rb_define_singleton_method(cNArray, "profile", na_profile, 0);
rb_define_singleton_method(cNArray, "profile=", na_profile_set, 1);
rb_define_singleton_method(cNArray, "inspect_rows", na_inspect_rows, 0);
rb_define_singleton_method(cNArray, "inspect_rows=", na_inspect_rows_set, 1);
rb_define_singleton_method(cNArray, "inspect_cols", na_inspect_cols, 0);
rb_define_singleton_method(cNArray, "inspect_cols=", na_inspect_cols_set, 1);
rb_define_method(cNArray, "initialize", na_initialize, -2);
rb_define_method(cNArray, "initialize_copy", na_initialize_copy, 1);
rb_define_singleton_method(cNArray, "zeros", na_s_zeros, -1);
rb_define_singleton_method(cNArray, "ones", na_s_ones, -1);
rb_define_singleton_method(cNArray, "linspace", na_s_linspace, -1);
rb_define_singleton_method(cNArray, "logspace", na_s_logspace, -1);
rb_define_singleton_method(cNArray, "eye", na_s_eye, -1);
rb_define_method(cNArray, "size", na_size, 0);
rb_define_alias (cNArray, "length","size");
rb_define_alias (cNArray, "total","size");
rb_define_method(cNArray, "shape", na_shape, 0);
rb_define_method(cNArray, "ndim", na_ndim,0);
rb_define_alias (cNArray, "rank","ndim");
rb_define_method(cNArray, "empty?", na_empty_p, 0);
rb_define_method(cNArray, "free", na_free, 0);
rb_define_method(cNArray, "debug_info", nary_debug_info, 0);
rb_define_method(cNArray, "contiguous?", na_check_contiguous, 0);
rb_define_method(cNArray, "fortran_contiguous?", na_check_fortran_contiguous, 0);
rb_define_method(cNArray, "view", na_make_view, 0);
rb_define_method(cNArray, "expand_dims", na_expand_dims, 1);
rb_define_method(cNArray, "reverse", nary_reverse, -1);
rb_define_singleton_method(cNArray, "upcast", numo_na_upcast, 1);
rb_define_singleton_method(cNArray, "byte_size", nary_s_byte_size, 0);
rb_define_singleton_method(cNArray, "from_binary", nary_s_from_binary, -1);
rb_define_alias (rb_singleton_class(cNArray), "from_string", "from_binary");
rb_define_method(cNArray, "store_binary", nary_store_binary, -1);
rb_define_method(cNArray, "to_binary", nary_to_binary, 0);
rb_define_alias (cNArray, "to_string", "to_binary");
rb_define_method(cNArray, "marshal_dump", nary_marshal_dump, 0);
rb_define_method(cNArray, "marshal_load", nary_marshal_load, 1);
rb_define_method(cNArray, "byte_size", nary_byte_size, 0);
rb_define_method(cNArray, "cast_to", nary_cast_to, 1);
rb_define_method(cNArray, "coerce", nary_coerce, 1);
rb_define_method(cNArray, "column_major?", na_column_major_p, 0);
rb_define_method(cNArray, "row_major?", na_row_major_p, 0);
rb_define_method(cNArray, "byte_swapped?", na_byte_swapped_p, 0);
rb_define_method(cNArray, "host_order?", na_host_order_p, 0);
rb_define_method(cNArray, "inplace", na_inplace, 0);
rb_define_method(cNArray, "inplace?", na_inplace_p, 0);
rb_define_method(cNArray, "inplace!", na_inplace_bang, 0);
rb_define_method(cNArray, "out_of_place!", na_out_of_place_bang, 0);
rb_define_alias (cNArray, "not_inplace!", "out_of_place!");
rb_define_method(cNArray, "==", na_equal, 1);
ndloop.c
- 該当なし
- おそらく内部処理のループ構造の実装。
rand.c
- 乱数生成に関するメソッドを実装。
-
srand
:乱数のシード設定。
-
rb_define_singleton_method(cNArray, "srand", nary_s_srand, -1);
step.c
- エイリアス
%
をstep
に関連付け。
rb_define_alias(rb_cRange, "%", "step");
struct.c
-
Numo::Struct
クラスに関するメソッドを実装。- 新しい型の定義:
new
、add_type
、型ごとのメソッド(例:int8
、sfloat
)。 - フィールド操作:
field
、field_set
。 - 配列への変換やストレージ操作:
to_a
、store
。 - メソッド動的定義:
method_missing
。
- 新しい型の定義:
st = rb_define_class_id(name, klass);
st = rb_define_class_under(klass, rb_id2name(id), klass);
rb_define_const(st, CONTIGUOUS_STRIDE, size);
rb_define_const(st, ELEMENT_BYTE_SIZE, size);
rb_define_const(st, ELEMENT_BIT_SIZE, rb_funcall(size,'*',1,INT2FIX(8)));
rb_define_const(st, "DEFINITIONS", members);
rb_define_singleton_method(st, "new", rb_class_new_instance, -1);
//rb_define_singleton_method(st, "[]", rb_class_new_instance, -1);
rb_define_method(st, "allocate", nst_allocate, 0);
#define rb_define_singleton_alias(klass,name1,name2) \
rb_define_alias(rb_singleton_class(klass),name1,name2)
cT = rb_define_class_under(mNumo, "Struct", numo_cNArray);
//cNStMember = rb_define_class_under(cT, "Member", rb_cObject);
//rb_define_alloc_func(cNStMember, nst_member_s_allocate);
//rb_define_method(cNStMember, "initialize", nst_member_initialize, -1);
rb_define_singleton_method(cT, "new", nst_s_new, -1);
rb_define_singleton_method(cT, "add_type", nst_s_add_type, -1);
rb_define_singleton_method(cT, "int8", nst_s_int8, -1);
rb_define_singleton_method(cT, "int16", nst_s_int16, -1);
rb_define_singleton_method(cT, "int32", nst_s_int32, -1);
rb_define_singleton_method(cT, "int64", nst_s_int64, -1);
rb_define_singleton_method(cT, "uint8", nst_s_uint8, -1);
rb_define_singleton_method(cT, "uint16", nst_s_uint16, -1);
rb_define_singleton_method(cT, "uint32", nst_s_uint32, -1);
rb_define_singleton_method(cT, "uint64", nst_s_uint64, -1);
rb_define_singleton_method(cT, "sfloat", nst_s_sfloat, -1);
rb_define_singleton_alias (cT, "float32", "sfloat");
rb_define_singleton_method(cT, "scomplex", nst_s_scomplex, -1);
rb_define_singleton_alias (cT, "complex64", "scomplex");
rb_define_singleton_method(cT, "dfloat", nst_s_dfloat, -1);
rb_define_singleton_alias (cT, "float64", "dfloat");
rb_define_singleton_method(cT, "dcomplex", nst_s_dcomplex, -1);
rb_define_singleton_alias (cT, "complex128", "dcomplex");
rb_define_method(cT, "definition", nst_definition, 1);
rb_define_method(cT, "definitions", nst_definitions, 0);
rb_define_method(cT, "field", nst_field, 1);
rb_define_method(cT, "field_set", nst_field_set, 2);
rb_define_method(cT, "extract", nst_extract, 0);
rb_define_method(cT, "method_missing", nst_method_missing, -1);
//rb_define_method(cT, "fill", nary_nstruct_fill, 1);
//rb_define_method(cT, "debug_print", nary_nstruct_debug_print, 0);
rb_define_method(cT, "to_a", nary_struct_to_a, 0);
rb_define_method(cT, "store", nary_struct_store, 1);
rb_define_method(cT, "inspect", nary_struct_inspect, 0);
rb_define_singleton_method(cT, "cast", nary_struct_s_cast, 1);
rb_define_singleton_method(cT, "[]", nary_struct_s_cast, -2);
//rb_define_method(cT, "initialize", rb_struct_initialize, -2);
//rb_define_method(cT, "initialize_copy", rb_struct_init_copy, 1);
この Numo::Strct というのは今回初めて知りました。おそらく、普通に Numo::NArrayを使っているだけだと触らないオブジェクトだと思います。これは何をしているのでしょうね……。
t_uint8.c
型ごとのファイルは一例として、t_uint8.c のみ提示します。
-
Numo::UInt8
クラスに特化したメソッドを実装。- 型情報の定義やサイズ情報:
ELEMENT_BIT_SIZE
、ELEMENT_BYTE_SIZE
。 - 演算系メソッド:
+
、-
、*
、/
などの算術演算。 - 比較演算:
gt
、lt
など。 - 集約系:
sum
、min
、max
。 - 要素ごとの操作:
each
、map
。
- 型情報の定義やサイズ情報:
mNumo = rb_define_module("Numo");
cT = rb_define_class_under(mNumo, "UInt8", cNArray);
rb_define_const(cT, "UPCAST", hCast);
rb_define_const(cT,"ELEMENT_BIT_SIZE",INT2FIX(sizeof(dtype)*8));
rb_define_const(cT,"ELEMENT_BYTE_SIZE",INT2FIX(sizeof(dtype)));
rb_define_const(cT,"CONTIGUOUS_STRIDE",INT2FIX(sizeof(dtype)));
rb_define_const(cT,"MAX",M_MAX);
rb_define_const(cT,"MIN",M_MIN);
rb_define_alloc_func(cT, uint8_s_alloc_func);
rb_define_method(cT, "allocate", uint8_allocate, 0);
rb_define_method(cT, "extract", uint8_extract, 0);
rb_define_method(cT, "store", uint8_store, 1);
rb_define_singleton_method(cT, "cast", uint8_s_cast, 1);
rb_define_method(cT, "[]", uint8_aref, -1);
rb_define_method(cT, "[]=", uint8_aset, -1);
rb_define_method(cT, "coerce_cast", uint8_coerce_cast, 1);
rb_define_method(cT, "to_a", uint8_to_a, 0);
rb_define_method(cT, "fill", uint8_fill, 1);
rb_define_method(cT, "format", uint8_format, -1);
rb_define_method(cT, "format_to_a", uint8_format_to_a, -1);
rb_define_method(cT, "inspect", uint8_inspect, 0);
rb_define_method(cT, "each", uint8_each, 0);
rb_define_method(cT, "map", uint8_map, 0);
rb_define_method(cT, "each_with_index", uint8_each_with_index, 0);
rb_define_method(cT, "map_with_index", uint8_map_with_index, 0);
rb_define_method(cT, "abs", uint8_abs, 0);
rb_define_method(cT, "+", uint8_add, 1);
rb_define_method(cT, "-", uint8_sub, 1);
rb_define_method(cT, "*", uint8_mul, 1);
rb_define_method(cT, "/", uint8_div, 1);
rb_define_method(cT, "%", uint8_mod, 1);
rb_define_method(cT, "divmod", uint8_divmod, 1);
rb_define_method(cT, "**", uint8_pow, 1);
rb_define_alias(cT, "pow", "**");
rb_define_method(cT, "-@", uint8_minus, 0);
rb_define_method(cT, "reciprocal", uint8_reciprocal, 0);
rb_define_method(cT, "sign", uint8_sign, 0);
rb_define_method(cT, "square", uint8_square, 0);
rb_define_alias(cT, "conj", "view");
rb_define_alias(cT, "im", "view");
rb_define_alias(cT, "conjugate", "conj");
rb_define_method(cT, "eq", uint8_eq, 1);
rb_define_method(cT, "ne", uint8_ne, 1);
rb_define_alias(cT, "nearly_eq", "eq");
rb_define_alias(cT, "close_to", "nearly_eq");
rb_define_method(cT, "&", uint8_bit_and, 1);
rb_define_method(cT, "|", uint8_bit_or, 1);
rb_define_method(cT, "^", uint8_bit_xor, 1);
rb_define_method(cT, "~", uint8_bit_not, 0);
rb_define_method(cT, "<<", uint8_left_shift, 1);
rb_define_method(cT, ">>", uint8_right_shift, 1);
rb_define_alias(cT, "floor", "view");
rb_define_alias(cT, "round", "view");
rb_define_alias(cT, "ceil", "view");
rb_define_alias(cT, "trunc", "view");
rb_define_alias(cT, "rint", "view");
rb_define_method(cT, "gt", uint8_gt, 1);
rb_define_method(cT, "ge", uint8_ge, 1);
rb_define_method(cT, "lt", uint8_lt, 1);
rb_define_method(cT, "le", uint8_le, 1);
rb_define_alias(cT, ">", "gt");
rb_define_alias(cT, ">=", "ge");
rb_define_alias(cT, "<", "lt");
rb_define_alias(cT, "<=", "le");
rb_define_method(cT, "clip", uint8_clip, 2);
rb_define_method(cT, "sum", uint8_sum, -1);
rb_define_method(cT, "prod", uint8_prod, -1);
rb_define_method(cT, "min", uint8_min, -1);
rb_define_method(cT, "max", uint8_max, -1);
rb_define_method(cT, "ptp", uint8_ptp, -1);
rb_define_method(cT, "max_index", uint8_max_index, -1);
rb_define_method(cT, "min_index", uint8_min_index, -1);
rb_define_method(cT, "argmax", uint8_argmax, -1);
rb_define_method(cT, "argmin", uint8_argmin, -1);
rb_define_method(cT, "minmax", uint8_minmax, -1);
rb_define_module_function(cT, "maximum", uint8_s_maximum, -1);
rb_define_module_function(cT, "minimum", uint8_s_minimum, -1);
rb_define_method(cT, "bincount", uint8_bincount, -1);
rb_define_method(cT, "cumsum", uint8_cumsum, -1);
rb_define_method(cT, "cumprod", uint8_cumprod, -1);
rb_define_method(cT, "mulsum", uint8_mulsum, -1);
rb_define_method(cT, "seq", uint8_seq, -1);
rb_define_method(cT, "eye", uint8_eye, -1);
rb_define_alias(cT, "indgen", "seq");
rb_define_method(cT, "rand", uint8_rand, -1);
rb_define_method(cT, "poly", uint8_poly, -2);
rb_define_method(cT, "sort", uint8_sort, -1);
rb_define_method(cT, "sort_index", uint8_sort_index, -1);
rb_define_method(cT, "median", uint8_median, -1);
rb_define_singleton_method(cT, "[]", uint8_s_cast, -2);
まとめ
今回は、Numo::NArrayに含まれているC言語のソースファイルから、rb_define
の行を抽出し、それぞれのファイルで、どのようなRubyのメソッドが定義されているのかの概要をつかみました。
なんだかこれだけでもかなり疲れた気がします。さすが Numo::NArray という感じです。
次回はいよいよ、narray.h などのヘッダーファイルから Numo::NArray を構成している構造体をピックアップして、どのような構造体があるのか調べてみたいと思います。
この記事は以上です。