ndav 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e27d78e17f22830ab47cba80120c7fa3ad3139690dd276e1267abb8696c6ffe3
4
- data.tar.gz: 1a3ab5273684a54a5920528663fa5ebcab8ab861213b622b60afe3267ff184e5
3
+ metadata.gz: '0194ddf5f19c3e973cb1ef947c125b598295db76eeca5998776612a592c40067'
4
+ data.tar.gz: 59f077dfdf2cdb48c675cde2e9addd7094a01417210fe28f8d786b93213ff8f8
5
5
  SHA512:
6
- metadata.gz: 6fe639a15fd780e5add9dcf124f3222214f6dd82cef28e4c16589f0f9dae936dda08e378ed28a3fa9bb810933250caf48bad2a18885a6261685451b3faf14bf0
7
- data.tar.gz: 3b3769ce7d12622bd231d0f4bffeb590b631a4c6be42984e2c998c49b64d2c24499bf922744d11b02c27dd308735c8f29d3385c70d5f3753509ce3bd70d4415f
6
+ metadata.gz: e0ff36126d7192ace3dba90a706ec0f45032c590dd5085c0ca1e4166c6a6708b518ad7077ef9af54a6def14911e3a62c3bbda0e0d904cea0dd51ab46a9fb25dd
7
+ data.tar.gz: 97026defa9b86987290f46a5f04ae0133ca280a87bea04989954fb49055ccc230a5f33d5ac90a8d68c046a05dcfaa97c46c7d6f93fda90f4234f7bf074ae1293
data/.yardopts CHANGED
@@ -1,3 +1,4 @@
1
- --asset NDAV.png
1
+ --title="NDAV - N-Dimensional Array View" -M commonmarker
2
2
  -
3
3
  LICENSE.txt
4
+ lib/ndav/ffi.rb
data/README.md CHANGED
@@ -7,7 +7,7 @@ A thin wrapper around [MemoryView][] ("buffer protocol" for Ruby).
7
7
 
8
8
  It provides an interoperability layer for multi-dimensional arrays which can be shared between libraries.
9
9
 
10
- ![NDAV converts library data each other](./NDAV.png)
10
+ ![NDAV converts library data each other](https://i.gyazo.com/905f541f15c0d57db5fc7ea3f6edaf07.png)
11
11
 
12
12
  SYNOPSIS
13
13
  --------
@@ -57,7 +57,9 @@ It might not be difficult, but a little bit cumbersome. Additionally, Red Arrow
57
57
  USAGE
58
58
  -----
59
59
 
60
- `ndav` gem is just a base library. You need to install bridges as well. Say, assume you want to make conversions between Numo::NArray each other.
60
+ `ndav` gem is just a base library. You need to install bridges as well.
61
+
62
+ Say, assume you want to make conversions between Numo::NArray each other.
61
63
 
62
64
  require "numo/narray"
63
65
  require "ndav"
@@ -70,7 +72,7 @@ USAGE
70
72
  ndav = NDAV.from_numo_narray(numo) # => NDAV
71
73
 
72
74
  include NDAV::Converter
73
- numo = NumoNArray(ndav) #=> Numo::SFloat
75
+ numo = NumoNArray(ndav) # => Numo::SFloat
74
76
  ndav = NDAV(numo) # => NDAV
75
77
 
76
78
  For `Torch::Tensor` and `OnnxRuntime::OrtValue`, you can do the same operation, therefore you may convert them to each other, like this:
@@ -100,12 +102,18 @@ NDAV can be initialized *directly* from libraries which export [MemoryView][] su
100
102
  arrow = Arrow::Int16Array.new([1, 2, 3])
101
103
  ndav = NDAV.new(arrow)
102
104
 
103
- On the other hand, it also exports MemoryView. You can pass NDAV arrays *directly* to methods which accept MemoryView such as whispercpp without bridge libraries:
105
+ On the other hand, it also exports MemoryView. You can pass NDAV arrays *directly* to methods which accept MemoryView such as [whispercpp][] without bridge libraries:
104
106
 
105
107
  waveform, sample_rate = TorchAudio.load("path/to/audio.wav")
106
108
  samples = waveform.to_ndav
107
109
  whisper.full(params, samples)
108
110
 
111
+ Additinally, classes that `include` `NDAV::MemoryViewable`, including `Numo::NArray`, `OnnxRuntime::OrtValue` and `Torch::Tensor`, export MemoryViews:
112
+
113
+ memory_view = Fiddle::MemoryView.new(numo)
114
+ memory_view = Fiddle::MemoryView.new(ort_value)
115
+ memory_view = Fiddle::MemoryView.new(torch_tensor)
116
+
109
117
  ### Notice On Memory Sharing ###
110
118
 
111
119
  Notice that NDAV is just a memory view and libraries share a memory address. If you change source data destructively, it affects converted data.
@@ -128,12 +136,18 @@ BRIDGES
128
136
 
129
137
  There are some bridges using NDAV:
130
138
 
131
- * [ndav/ffi][]: [`FFI::MemoryPointer`][FFI Pointer], [`FFI::Pointer`][FFI Pointer] <-> `NDAV`
139
+ * {file:ndav/ffi ndav/ffi}: [`FFI::MemoryPointer`][FFI Pointer], [`FFI::Pointer`][FFI Pointer] <-> `NDAV`
132
140
  * [ndav-numo-narray][]: [`Numo::NArray`][Numo::NArray] <-> `NDAV`
133
141
  * [ndav-ort_value][]: [`OnnxRuntime::OrtValue`][ONNX Runtime Ruby] <-> `NDAV`
134
142
  * [ndav-torch-tensor][]: [`Torch::Tensor`][Torch.rb] <-> `NDAV`
135
143
  * [onnxruntime-torch-tensor][]: [`Torch::Tensor`][Torch.rb] <-> [`OnnxRuntime::OrtValue`][ONNX Runtime Ruby] via `NDAV`
136
144
 
145
+ APPLICATIONS AND LIBRARIES WHICH USE NDAV
146
+ -----------------------------------------
147
+
148
+ * [GTCRN][] - An audio speech enhancement (noise reduction) library. It uses [TorchAudio][] for loading and preprocessing audio before passing it to ONNX Runtime, then performs post-processing on the result and writes it back to a file.
149
+ * [Itak][] - An audio processing tool for podcasters. After reducing noise with the GTCRN mentioned above, it uses the VAD function of [whispercpp][] to remove silent periods. Although whispercpp does not accept existing tensor libraries, it does accept MemoryView, so it can be passed via NDAV.
150
+
137
151
  CREATING BRIDGES
138
152
  ----------------
139
153
 
@@ -141,8 +155,8 @@ Refer to existing bridge implementations listed above to create your bridge.
141
155
 
142
156
  The points are:
143
157
 
144
- * Implement `FromNDAV#from_ndav`, `ToNDAV#to_ndav` and `NDAV.register` them, and `NDAV.from_your_data` and `NDAV#to_your_data` are automatically derived
145
- * When initializing NDAV object from your object, use `lifetime` keyword argument for `NDAV.new` effectively to prevent Ruby from GCing your object, which would lead to a dangling pointer
158
+ * Implement `FromNDAV#from_ndav`, `MemoryViewable#ndav_descriptor`, optionally `ToNDAV#to_ndav`, {NDAV.register register} them, and `NDAV.from_your_data` and `NDAV#to_your_data` are automatically derived and the array become to able to export a MemoryView
159
+ * When initializing NDAV object from your object, use `lifetime` keyword argument for {NDAV#initialize} effectively to prevent Ruby from GCing your object, which would lead to a dangling pointer
146
160
  * When initializing your object from NDAV object, keep NDAV object alive to prevent Ruby from GCing NDAV object, which would lead to a dangling pointer, [ndav-numo-narray][], for instance, embeds the NDAV object in an instance variable
147
161
 
148
162
  An advantage of NDAV over raw MemoryView is that you can write bridges in pure Ruby in most cases. It helps prototyping and experimentation. As an exception, I had to write C code for ndav-numo-narray because Numo::NArray only provides methods that access data by copying and does not directly expose its raw data pointer to Ruby API. However, in even such case, pure Ruby bridge remains a viable option for prototyping and experiments where a single initial copy is acceptable.
@@ -157,7 +171,7 @@ If [MemoryView][] gets popular enough in the Ruby ecosystem, this library will e
157
171
  LICENSE
158
172
  -------
159
173
 
160
- BSD-2-Clause license. See LICENSE.txt file.
174
+ BSD-2-Clause license. See {file:LICENSE.txt} file.
161
175
 
162
176
  [MemoryView]: https://docs.ruby-lang.org/en/master/contributing/memory_view_md.html
163
177
  [Numo::NArray]: https://ruby-numo.github.io/narray/
@@ -165,9 +179,13 @@ BSD-2-Clause license. See LICENSE.txt file.
165
179
  [ONNX Runtime Ruby]: https://github.com/ankane/onnxruntime-ruby
166
180
  [Red Arrow]: https://github.com/apache/arrow/tree/main/ruby
167
181
  [Red Arrow Numo::NArray]: https://github.com/red-data-tools/red-arrow-numo-narray
168
- [ndav/ffi]: https://gitlab.com/KitaitiMakoto/ndav/-/blob/main/lib/ndav/ffi.rb?ref_type=heads
182
+ [ndav/ffi]: https://gitlab.com/KitaitiMakoto/ndav/-/blob/main/lib/ndav/ffi.rb
169
183
  [FFI Pointer]: https://github.com/ffi/ffi/wiki/Pointers
184
+ [whispercpp]: https://github.com/ggml-org/whisper.cpp/tree/master/bindings/ruby
170
185
  [ndav-numo-narray]: https://gitlab.com/KitaitiMakoto/ndav-numo-narray
171
186
  [ndav-ort_value]: https://gitlab.com/KitaitiMakoto/ndav-ort_value
172
187
  [ndav-torch-tensor]: https://gitlab.com/KitaitiMakoto/ndav-torch-tensor
173
188
  [onnxruntime-torch-tensor]: https://gitlab.com/KitaitiMakoto/onnxruntime-torch-tensor
189
+ [GTCRN]: https://gitlab.com/KitaitiMakoto/gtcrn
190
+ [TorchAudio]: https://github.com/ankane/torchaudio-ruby
191
+ [Itak]: https://gitlab.com/KitaitiMakoto/itak
data/ext/ndav.c CHANGED
@@ -1,145 +1,250 @@
1
- #include "ndav.h"
1
+ #include <ruby.h>
2
+ #include <ruby/memory_view.h>
3
+
4
+ static ID id_ndav_validated_descriptor;
5
+ static VALUE sym_addr;
6
+ static VALUE sym_shape;
7
+ static VALUE sym_strides;
8
+ static VALUE sym_format;
9
+ static VALUE sym_byte_size;
10
+ static VALUE sym_readonly_p;
11
+ static VALUE sym_sub_offsets;
12
+ static VALUE sym_ndim;
13
+ static VALUE sym_item_size;
14
+ static VALUE sym_row_major_contiguous_p;
15
+ static VALUE sym_column_major_contiguous_p;
16
+
17
+ typedef struct {
18
+ VALUE descriptor;
19
+ } private_data_t;
2
20
 
3
- ID id_to_s;
4
-
5
- typedef struct fill_size_array_args_t {
6
- VALUE src;
7
- ssize_t *dest;
8
- ssize_t size;
9
- } fill_size_array_args_t;
10
-
11
- static VALUE
12
- fill_size_array(VALUE rb_args)
21
+ static bool
22
+ fill_size_array(VALUE src, ssize_t *dest, ssize_t size)
13
23
  {
14
- struct fill_size_array_args_t *args = (fill_size_array_args_t *)rb_args;
15
- for (ssize_t i = 0; i < args->size; i++) {
16
- VALUE val = rb_ary_entry(args->src, i);
17
- args->dest[i] = NUM2SSIZET(val);
24
+ for (ssize_t i = 0; i < size; i++) {
25
+ VALUE val = rb_ary_entry(src, i);
26
+ if (!RB_INTEGER_TYPE_P(val)) {
27
+ return false;
28
+ }
29
+ dest[i] = NUM2SSIZET(val);
18
30
  }
19
- return Qnil;
31
+ return true;
32
+ }
33
+
34
+ static void
35
+ release_memview_common(rb_memory_view_t *view)
36
+ {
37
+ if (view->shape) {
38
+ free((void *)view->shape);
39
+ view->shape = NULL;
40
+ }
41
+ if (view->strides) {
42
+ free((void *)view->strides);
43
+ view->strides = NULL;
44
+ }
45
+ if (view->sub_offsets) {
46
+ free((void *)view->sub_offsets);
47
+ view->sub_offsets = NULL;
48
+ }
20
49
  }
21
50
 
22
51
  static bool
23
- ndav_get_memory_view(const VALUE ndav, rb_memory_view_t *view, int flags)
52
+ ndav_memory_viewable_get_memory_view(const VALUE ndav, rb_memory_view_t *view, int flags)
24
53
  {
25
54
  bool row_major_requested = (flags & RUBY_MEMORY_VIEW_ROW_MAJOR) == RUBY_MEMORY_VIEW_ROW_MAJOR;
26
55
  bool column_major_requested = (flags & RUBY_MEMORY_VIEW_COLUMN_MAJOR) == RUBY_MEMORY_VIEW_COLUMN_MAJOR;
27
56
  bool indirect_requested = (flags & RUBY_MEMORY_VIEW_INDIRECT) == RUBY_MEMORY_VIEW_INDIRECT;
28
- if ((column_major_requested && !row_major_requested) || indirect_requested) {
57
+ VALUE desc = rb_funcall(ndav, id_ndav_validated_descriptor, 1, INT2NUM(flags));
58
+ if (!RB_TYPE_P(desc, T_HASH)) {
59
+ rb_warn("descriptor not Hash");
29
60
  return false;
30
61
  }
31
- bool writable_requested = (flags & RUBY_MEMORY_VIEW_WRITABLE) == RUBY_MEMORY_VIEW_WRITABLE;
32
- // TODO: Handle flags
33
-
34
- VALUE readonly = rb_iv_get(ndav, "@readonly");
35
- if (NIL_P(readonly)) {
36
- if (writable_requested) {
37
- rb_warn("not writable");
62
+ bool row_major_contiguous = RTEST(rb_hash_aref(desc, sym_row_major_contiguous_p));
63
+ bool column_major_contiguous = RTEST(rb_hash_aref(desc, sym_column_major_contiguous_p));
64
+ if (row_major_requested && column_major_requested) { // row-major OR column-major requested
65
+ if (!row_major_contiguous && !column_major_contiguous) {
38
66
  return false;
39
67
  }
40
- view->readonly = true;
41
- } else {
42
- view->readonly = RTEST(readonly);
43
68
  }
44
- view->obj = ndav;
45
- VALUE val = rb_funcall(ndav, id_to_s, 0);
46
- char *data = StringValuePtr(val);
47
- view->data = data;
69
+ // Currently, non-contiguous array not supported
70
+ else if (row_major_requested && !row_major_contiguous) {
71
+ return false;
72
+ }
73
+ // Currently, non-contiguous array not supported
74
+ else if (column_major_requested && !column_major_contiguous) {
75
+ return false;
76
+ }
77
+ bool writable_requested = (flags & RUBY_MEMORY_VIEW_WRITABLE) == RUBY_MEMORY_VIEW_WRITABLE;
78
+ // TODO: Handle other flags
79
+
80
+ VALUE readonly_v = rb_hash_aref(desc, sym_readonly_p);
81
+ bool readonly = NIL_P(readonly_v) || RTEST(readonly_v);
82
+ if (writable_requested && readonly) {
83
+ rb_warn("not writable");
84
+ return false;
85
+ }
86
+ VALUE byte_size_v = rb_hash_aref(desc, sym_byte_size);
87
+ ssize_t byte_size = NUM2SSIZET(byte_size_v);
88
+ VALUE addr = rb_hash_aref(desc, sym_addr);
89
+ rb_memory_view_t tmp = {0};
90
+ if (!rb_memory_view_init_as_byte_array(&tmp, ndav, NUM2PTR(addr), byte_size, readonly)) {
91
+ rb_warn("failed to initialize");
92
+ return false;
93
+ }
94
+ tmp.readonly = readonly;
95
+ tmp.obj = ndav;
48
96
  // TODO: Commonalize
49
97
  // TODO: Consider:
50
98
  // * Use TypedData?
51
99
  // * If so, calling attr readers at Ruby layer has performance disadvantage.
52
100
  // * Freezing instance vars in #initialize and then embed them to TypedData may be a solution
53
- VALUE item_size = rb_iv_get(ndav, "@item_size");
54
- view->item_size = NUM2SSIZET(item_size);
55
- VALUE byte_size = rb_iv_get(ndav, "@byte_size");
56
- view->byte_size = NUM2SSIZET(byte_size);
57
- VALUE ndim = rb_iv_get(ndav, "@ndim");
58
- view->ndim = NUM2SSIZET(ndim);
59
- VALUE format = rb_iv_get(ndav, "@format");
60
- view->format = StringValueCStr(format);
61
-
62
- int state;
101
+ VALUE item_size = rb_hash_aref(desc, sym_item_size);
102
+ tmp.item_size = NUM2SSIZET(item_size);
103
+ tmp.byte_size = byte_size;
104
+ VALUE ndim = rb_hash_aref(desc, sym_ndim);
105
+ tmp.ndim = NUM2SSIZET(ndim);
106
+ VALUE format = rb_hash_aref(desc, sym_format);
107
+ tmp.format = StringValueCStr(format);
63
108
 
64
109
  // TODO: Commonalize
65
- VALUE shape = rb_iv_get(ndav, "@shape");
110
+ VALUE shape = rb_hash_aref(desc, sym_shape);
66
111
  if (!RB_TYPE_P(shape, T_ARRAY)) {
67
- rb_warn("@shape is not an array");
112
+ release_memview_common(&tmp);
113
+ rb_warn("shape is not an array");
68
114
  return false;
69
115
  }
70
- ssize_t *view_shape = ALLOC_N(ssize_t, view->ndim);
71
- fill_size_array_args_t shape_args = {
72
- shape,
73
- view_shape,
74
- view->ndim,
75
- };
76
- rb_protect(fill_size_array, (VALUE)&shape_args, &state);
77
- if (state) {
78
- xfree(view_shape);
79
- rb_jump_tag(state);
116
+ ssize_t *view_shape = (ssize_t *)malloc(sizeof(ssize_t) * tmp.ndim);
117
+ if (!view_shape) {
118
+ release_memview_common(&tmp);
80
119
  return false;
81
120
  }
82
- view->shape = view_shape;
121
+ if (!fill_size_array(shape, view_shape, tmp.ndim)) {
122
+ free(view_shape);
123
+ release_memview_common(&tmp);
124
+ return false;
125
+ }
126
+ tmp.shape = view_shape;
83
127
 
84
- VALUE strides = rb_iv_get(ndav, "@strides");
128
+ VALUE strides = rb_hash_aref(desc, sym_strides);
85
129
  if (!RB_TYPE_P(strides, T_ARRAY)) {
86
- rb_warn("@strides is not an array");
87
- xfree((void *)view->shape);
130
+ release_memview_common(&tmp);
131
+ rb_warn("strides is not an array");
132
+ return false;
133
+ }
134
+ ssize_t *view_strides = (ssize_t *)malloc(sizeof(ssize_t) * tmp.ndim);
135
+ if (!view_strides) {
136
+ release_memview_common(&tmp);
88
137
  return false;
89
138
  }
90
- ssize_t *view_strides = ALLOC_N(ssize_t, view->ndim);
91
- fill_size_array_args_t strides_args = {
92
- strides,
93
- view_strides,
94
- view->ndim
95
- };
96
- rb_protect(fill_size_array, (VALUE)&strides_args, &state);
97
- if (state) {
98
- xfree((void *)view_shape);
99
- xfree((void *)view_strides);
100
- rb_jump_tag(state);
139
+ if (!fill_size_array(strides, view_strides, tmp.ndim)) {
140
+ free(view_strides);
141
+ release_memview_common(&tmp);
101
142
  return false;
102
143
  }
103
- view->strides = view_strides;
144
+ tmp.strides = view_strides;
104
145
 
105
- view->sub_offsets = NULL;
146
+ VALUE sub_offsets = rb_hash_aref(desc, sym_sub_offsets);
147
+ if (NIL_P(sub_offsets)) {
148
+ if (indirect_requested) {
149
+ release_memview_common(&tmp);
150
+ rb_warn("indirect requested but sub_offsets is NULL");
151
+ return false;
152
+ }
153
+ tmp.sub_offsets = NULL;
154
+ } else if (!RB_TYPE_P(sub_offsets, T_ARRAY)) {
155
+ release_memview_common(&tmp);
156
+ rb_warn("sub_offsets is not an array");
157
+ return false;
158
+ } else {
159
+ ssize_t *view_sub_offsets = (ssize_t *)malloc(sizeof(ssize_t) * tmp.ndim);
160
+ if (!view_sub_offsets) {
161
+ release_memview_common(&tmp);
162
+ return false;
163
+ }
164
+ if (!fill_size_array(sub_offsets, view_sub_offsets, tmp.ndim)) {
165
+ free(view_sub_offsets);
166
+ release_memview_common(&tmp);
167
+ return false;
168
+ }
169
+ tmp.sub_offsets = view_sub_offsets;
170
+ }
171
+
172
+ private_data_t *private_data = malloc(sizeof(private_data_t));
173
+ if (!private_data) {
174
+ release_memview_common(&tmp);
175
+ rb_warn("failed to alloc private_data");
176
+ return false;
177
+ }
178
+ private_data->descriptor = Qnil;
179
+ rb_gc_register_address(&private_data->descriptor);
180
+ private_data->descriptor = desc;
181
+ tmp.private_data = private_data;
182
+
183
+ *view = tmp;
106
184
 
107
185
  return true;
108
186
  }
109
187
 
110
188
  static bool
111
- ndav_release_memory_view(const VALUE ndav, rb_memory_view_t *view)
189
+ ndav_memory_viewable_release_memory_view(const VALUE ndav, rb_memory_view_t *view)
112
190
  {
113
- if (view->shape) {
114
- xfree((void *)view->shape);
115
- view->shape = NULL;
116
- }
117
- if (view->strides) {
118
- xfree((void *)view->strides);
119
- view->strides = NULL;
191
+ release_memview_common(view);
192
+ if (view->private_data) {
193
+ private_data_t *private_data = (private_data_t *)view->private_data;
194
+ rb_gc_unregister_address(&private_data->descriptor);
195
+ free(private_data);
196
+ view->private_data = NULL;
120
197
  }
121
198
 
122
199
  return true;
123
200
  }
124
201
 
125
202
  static bool
126
- ndav_memory_view_available_p(const VALUE obj)
203
+ ndav_memory_viewable_memory_view_available_p(const VALUE obj)
127
204
  {
128
- VALUE fmv = rb_iv_get(obj, "@fmv");
129
- return !NIL_P(fmv);
205
+ VALUE descriptor = rb_funcall(obj, id_ndav_validated_descriptor, 0);
206
+ if (RB_TYPE_P(descriptor, T_HASH)) {
207
+ return true;
208
+ } else {
209
+ rb_warn("descriptor not Hash");
210
+ return false;
211
+ }
130
212
  }
131
213
 
132
214
  const struct rb_memory_view_entry ndav_view_entry = {
133
- ndav_get_memory_view,
134
- ndav_release_memory_view,
135
- ndav_memory_view_available_p
215
+ ndav_memory_viewable_get_memory_view,
216
+ ndav_memory_viewable_release_memory_view,
217
+ ndav_memory_viewable_memory_view_available_p
136
218
  };
137
219
 
220
+ static VALUE
221
+ ndav_memory_viewable_s_register(VALUE mod, VALUE klass)
222
+ {
223
+ if (!rb_memory_view_register(klass, &ndav_view_entry)) {
224
+ // error message output in rb_memory_view_register()
225
+ rb_raise(rb_eArgError, "");
226
+ }
227
+
228
+ return Qtrue;
229
+ }
230
+
138
231
  void
139
232
  Init_ndav(void)
140
233
  {
141
- id_to_s = rb_intern("to_s");
234
+ id_ndav_validated_descriptor = rb_intern("ndav_validated_descriptor");
235
+ sym_addr = ID2SYM(rb_intern("addr"));
236
+ sym_shape = ID2SYM(rb_intern("shape"));
237
+ sym_strides = ID2SYM(rb_intern("strides"));
238
+ sym_format = ID2SYM(rb_intern("format"));
239
+ sym_byte_size = ID2SYM(rb_intern("byte_size"));
240
+ sym_readonly_p = ID2SYM(rb_intern("readonly?"));
241
+ sym_sub_offsets = ID2SYM(rb_intern("sub_offsets"));
242
+ sym_ndim = ID2SYM(rb_intern("ndim"));
243
+ sym_item_size = ID2SYM(rb_intern("item_size"));
244
+ sym_row_major_contiguous_p = ID2SYM(rb_intern("row_major_contiguous?"));
245
+ sym_column_major_contiguous_p = ID2SYM(rb_intern("column_major_contiguous?"));
142
246
 
143
247
  VALUE cNDAV = rb_define_class("NDAV", rb_cObject);
144
- rb_memory_view_register(cNDAV, &ndav_view_entry);
248
+ VALUE mMemoryViewable = rb_define_module_under(cNDAV, "MemoryViewable");
249
+ rb_define_singleton_method(mMemoryViewable, "register", ndav_memory_viewable_s_register, 1);
145
250
  }
@@ -17,6 +17,19 @@ class NDAV
17
17
  end
18
18
  end
19
19
 
20
+ if mdl.const_defined?(:MemoryViewable)
21
+ cls.include ::NDAV::MemoryViewable
22
+ cls.include mdl::MemoryViewable
23
+
24
+ unless mdl.const_defined?(:ToNDAV)
25
+ mdl.const_set(:ToNDAV, Module.new {
26
+ def to_ndav(lifetime: self, **)
27
+ ::NDAV.new(self, lifetime:, **)
28
+ end
29
+ })
30
+ end
31
+ end
32
+
20
33
  if mdl.const_defined?(:ToNDAV)
21
34
  cls.include mdl::ToNDAV
22
35
 
data/lib/ndav/ffi.rb CHANGED
@@ -26,6 +26,25 @@ class NDAV
26
26
  end
27
27
  end
28
28
 
29
+ module MemoryViewable
30
+ def ndav_descriptor(**)
31
+ shape = [size / type_size]
32
+ format = TYPE_SIZE_TO_FORMAT[type_size]
33
+ item_size = ITEM_SIZES[format]
34
+ byte_size = shape.reduce(item_size, :*)
35
+ ptr = ::Fiddle::Pointer.new(address, byte_size)
36
+
37
+ {
38
+ data: Fiddle::MemoryView.new(ptr).to_s,
39
+ shape:,
40
+ strides: ::NDAV.default_strides(shape:, item_size:),
41
+ format:,
42
+ byte_size:,
43
+ readonly?: true
44
+ }
45
+ end
46
+ end
47
+
29
48
  module Converter
30
49
  def FFIMemoryPointer(array, *, **)
31
50
  if ::FFI::MemoryPointer === array
data/lib/ndav/flags.rb ADDED
@@ -0,0 +1,26 @@
1
+ class NDAV
2
+ module Flags
3
+ FLAGS = {}
4
+ FLAGS[:simple] = 0
5
+ FLAGS[:writable] = (1<<0)
6
+ FLAGS[:format] = (1<<1)
7
+ FLAGS[:multi_dimensional] = (1<<2)
8
+ FLAGS[:strides] = (1<<3) | FLAGS[:multi_dimensional]
9
+ FLAGS[:row_major] = (1<<4) | FLAGS[:strides]
10
+ FLAGS[:column_major] = (1<<5) | FLAGS[:strides]
11
+ FLAGS[:any_contiguous] = FLAGS[:row_major] | FLAGS[:column_major]
12
+ FLAGS[:indirect] = (1<<6) | FLAGS[:strides]
13
+
14
+ module_function
15
+
16
+ def decode(flags)
17
+ FLAGS.each_with_object({}) {|(name, value), state|
18
+ if name == :simple
19
+ state[name] = flags == 0
20
+ else
21
+ state[name] = flags & value == value
22
+ end
23
+ }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,80 @@
1
+ class NDAV
2
+ # +include+ this module and define +#ndav_descriptor+ method in your class,
3
+ # and then the class exports a MemoryView.
4
+ #
5
+ # +#ndav_descriptor+'s arguments are flags of MemoryView including
6
+ # +simple+, +writable+, +format+, +multi_dimensional+, +strides+, +row_major+, +column_major+, +any_contiguous+, +indirect+.
7
+ # For the meaning of them, see MemoryView document.
8
+ #
9
+ # It must return a Hash whose keys includes:
10
+ #
11
+ # * +:data+: Required. Binary +String+, pointer address +Integer+ or +Fiddle::Pointer+. This is passed to +Fiddle::Pointer.to_ptr+, so should be valid for the method.
12
+ # * +:shape+: Required. See MemoryView document.
13
+ # * +:strides+: Required. See MemoryView document.
14
+ # * +:format+: Required. See MemoryView document.
15
+ # * +:byte_size+: Required. See MemoryView document.
16
+ # * +:readonly?+: Required. See MemoryView document.
17
+ # * +:sub_offsets+: Optional. See MemoryView document.
18
+ #
19
+ # @example
20
+ # class YourClass
21
+ # include NDAV::MemoryViewable
22
+ # # :
23
+ # def ndav_descriptor(
24
+ # simple: true,
25
+ # writable: false,
26
+ # format: false,
27
+ # multi_dimensional: false,
28
+ # strides: false,
29
+ # row_major: false,
30
+ # column_major: false,
31
+ # any_contiguous: false,
32
+ # indirect: false
33
+ # )
34
+ # {
35
+ # data:,
36
+ # shape:,
37
+ # strides:,
38
+ # format:,
39
+ # byte_size:,
40
+ # readonly?:,
41
+ # sub_offsets:
42
+ # }
43
+ # end
44
+ # # :
45
+ # end
46
+ module MemoryViewable
47
+ class << self
48
+ def included(base)
49
+ ::NDAV::MemoryViewable.register base
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def ndav_validated_descriptor(flags = ::NDAV::Flags::FLAGS[:simple])
56
+ desc = ndav_descriptor(**NDAV::Flags.decode(flags)).to_h
57
+ [:data, :shape, :strides, :format, :byte_size, :readonly?].each do |key|
58
+ unless desc.key? key
59
+ warn ":#{key} not in descriptor"
60
+ return false
61
+ end
62
+ end
63
+ item_size = NDAV::ITEM_SIZES[desc[:format]]
64
+ unless desc[:shape].reduce(item_size, :*) == desc[:byte_size]
65
+ warn "item size from format and shape not match byte_size"
66
+ return false
67
+ end
68
+ desc.merge(
69
+ addr: ::Fiddle::Pointer.to_ptr(desc[:data]).to_i,
70
+ ndim: desc[:shape].length,
71
+ item_size:,
72
+ row_major_contiguous?: ::NDAV.row_major_contiguous?(**desc),
73
+ column_major_contiguous?: ::NDAV.column_major_contiguous?(**desc)
74
+ )
75
+ rescue => err
76
+ warn err
77
+ return false
78
+ end
79
+ end
80
+ end
data/lib/ndav.rb CHANGED
@@ -1,88 +1,143 @@
1
1
  require "fiddle"
2
2
  require "ndav.so"
3
+ require "ndav/flags"
3
4
  require "ndav/converter"
5
+ require "ndav/memory_viewable"
4
6
 
5
7
  # TODO: Manage flags of internal data object
6
8
  class NDAV
9
+ include MemoryViewable
10
+
7
11
  # TODO: Want to use return value of rb_memory_view_parse_item_format
8
12
  ITEM_SIZES = ["c", "C", "s", "S", "l", "L", "q", "Q", "f", "d", "s!", "S!", "n", "v", "i", "i!", "I", "I!", "l!", "L!", "N", "V", "e", "g", "q!", "Q!", "E", "G"].collect {|format|
9
13
  [format, [0].pack(format).bytesize]
10
14
  }.to_h
11
15
 
12
- attr_reader :ndim, :shape, :format, :item_size, :byte_size, :strides, :lifetime
16
+ attr_reader :ndim, :shape, :format, :item_size, :byte_size, :strides, :to_s, :lifetime
17
+
18
+ class << self
19
+ def default_strides(shape:, item_size:, row_major: true)
20
+ each_shape = row_major ? shape.reverse_each : shape.each
21
+ each_shape.reduce([[], item_size]) {|(strides, stride), s|
22
+ if row_major
23
+ strides.unshift stride
24
+ else
25
+ strides << stride
26
+ end
27
+ [strides, stride * s]
28
+ }[0]
29
+ end
30
+
31
+ # Algorithm stolen from memory_view.c
32
+ def row_major_contiguous?(format:, shape:, strides:, **)
33
+ ndim = shape.length
34
+ n = ITEM_SIZES[format]
35
+ return strides[0] == n if ndim == 1
36
+
37
+ (ndim - 1).downto 0 do |i|
38
+ return false unless strides[i] == n
39
+
40
+ n *= shape[i]
41
+ end
42
+
43
+ true
44
+ end
45
+
46
+ # Algorithm stolen from memory_view.c
47
+ def column_major_contiguous?(format:, shape:, strides:, **)
48
+ ndim = shape.length
49
+ n = ITEM_SIZES[format]
50
+ return strides[0] == n if ndim == 1
51
+
52
+ 0.upto(ndim - 1) do |i|
53
+ return false unless strides[i] == n
54
+
55
+ n *= shape[i]
56
+ end
57
+
58
+ true
59
+ end
60
+ end
13
61
 
14
62
  # TODO: More options to trick for the case of MemoryView export is wrong but developer don't fix it
15
- def initialize(input, shape: nil, format: nil, lifetime: nil)
16
- @fmv = input.kind_of?(::Fiddle::MemoryView) ? input : ::Fiddle::MemoryView.new(input)
17
- @lifetime = [@fmv.obj, lifetime].compact
18
- @lifetime = nil if @lifetime.empty?
63
+ def initialize(obj, shape: nil, strides: nil, format: nil, lifetime: nil)
64
+ fmv = obj.kind_of?(::Fiddle::MemoryView) ? obj : ::Fiddle::MemoryView.new(obj)
65
+ # Fiddle::MemoryView#to_s is zero-copy
66
+ # Fiddle::MemoryView#to_s embeds obj into the String, so we can prevent GC from collecting it
67
+ @to_s = fmv.to_s
68
+ @lifetime = lifetime
19
69
 
20
- @format = format || @fmv.format
70
+ @format = format || fmv.format
21
71
  raise ArgumentError, %Q|unsupported format: #{@format.to_s.dump}, currently supported: #{ITEM_SIZES.keys}| unless ITEM_SIZES.key?(@format)
22
72
 
23
73
  item_size_by_format = ITEM_SIZES[@format]
24
74
 
25
- @byte_size = @fmv.byte_size
26
- @item_size = input.kind_of?(::Fiddle::Pointer) ? item_size_by_format : @fmv.item_size || item_size_by_format
27
- @shape = shape || @fmv.shape
75
+ @byte_size = fmv.byte_size
76
+ @item_size = obj.kind_of?(::Fiddle::Pointer) ? item_size_by_format : fmv.item_size || item_size_by_format
77
+ @shape = shape || fmv.shape
28
78
  case [@shape, @byte_size]
29
79
  in [nil, nil]
30
- raise ArgumentError, "either shape or byte_size must be present in input or arguments"
31
- in [*, nil]
80
+ raise ArgumentError, "either shape or byte_size must be present in obj or arguments"
81
+ in [_, nil]
32
82
  @byte_size = @shape.reduce(@item_size, :*)
33
- in [nil, *]
83
+ in [nil, _]
34
84
  n, r = @byte_size.divmod(@item_size)
35
85
  raise ArgumentError, "byte_size must be n-times of item_size" unless r.zero?
36
86
  @shape = [n]
37
87
  else
38
88
  # noop
39
89
  end
40
- @strides = @fmv.strides || @shape.reverse_each.reduce([[], @item_size]) {|(strides, stride), s|
41
- strides.unshift(stride)
42
- [strides, stride * s]
43
- }[0]
44
90
  @ndim = @shape.size
45
- @readonly = @fmv.readonly?
91
+ @readonly = fmv.readonly?
92
+ @strides = strides || fmv.strides || self.class.default_strides(shape: @shape, item_size: @item_size)
93
+ @sub_offsets = fmv.sub_offsets
94
+ @row_major_contiguous = self.class.row_major_contiguous?(format: @format, shape: @shape, strides: @strides)
95
+ @column_major_contiguous = self.class.column_major_contiguous?(format: @format, shape: @shape, strides: @strides)
46
96
 
47
- validate
97
+ validate fmv
48
98
  end
49
99
 
50
100
  def readonly?
51
101
  @readonly
52
102
  end
53
103
 
54
- # Zero-copy
55
- def to_s
56
- # MemoryView#to_s is zero-copy
57
- # Assign to @data to keep reference to the string to prevent GC
58
- @data ||= @fmv.to_s
104
+ def row_major_contiguous?
105
+ @row_major_contiguous
106
+ end
107
+
108
+ def column_major_contiguous?
109
+ @column_major_contiguous
59
110
  end
60
111
 
61
112
  def to_ptr
62
- ::Fiddle::Pointer[to_s]
113
+ ::Fiddle::Pointer[@to_s]
63
114
  end
64
115
  alias to_fiddle_pointer to_ptr
65
116
 
66
117
  private
67
118
 
68
- def validate
119
+ def ndav_descriptor(**)
120
+ {
121
+ data: @to_s,
122
+ shape: @shape,
123
+ strides: @strides,
124
+ format: @format,
125
+ byte_size: @byte_size,
126
+ readonly?: @readonly,
127
+ sub_offsets: @sub_offsets
128
+ }
129
+ end
130
+
131
+ def validate(fmv)
69
132
  unless @item_size == ITEM_SIZES[@format]
70
133
  raise ArgumentError, "item_size does not match format"
71
134
  end
72
135
 
73
- if @fmv.sub_offsets
74
- raise ArgumentError, "suboffsets not supported"
75
- end
76
-
77
- stride = @item_size
78
- @shape.reverse_each.with_index do |s, i|
79
- unless @strides[@ndim - 1 - i] == stride
80
- raise ArgumentError, "only row-major contiguous strides is supported"
136
+ if @row_major_contiguous || @column_major_contiguous
137
+ byte_size = @shape.reduce(@item_size, :*)
138
+ unless byte_size == @byte_size
139
+ raise ArgumentError, "strides not match byte_size"
81
140
  end
82
- stride *= s
83
- end
84
- unless stride == @byte_size
85
- raise ArgumentError, "strides not match byte_size"
86
141
  end
87
142
  end
88
143
  end
data/ndav.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ndav"
3
- s.version = "0.0.4"
3
+ s.version = "0.0.6"
4
4
  s.summary = "N-Dimensional Array View"
5
5
  s.authors = ["Kitaiti Makoto"]
6
6
  s.licenses = ["BSD-2-Clause"]
@@ -22,4 +22,5 @@ Gem::Specification.new do |s|
22
22
  s.add_development_dependency "red-arrow"
23
23
  s.add_development_dependency "ffi"
24
24
  s.add_development_dependency "yard"
25
+ s.add_development_dependency "commonmarker"
25
26
  end
data/test/test_ffi.rb CHANGED
@@ -48,6 +48,14 @@ class TestFFI < TestBase
48
48
  assert_equal ndav.to_ptr.to_i, ptr.address
49
49
  end
50
50
 
51
+ def test_memory_view_from_ffi_memory_pointer
52
+ ptr = ::FFI::MemoryPointer.new(:int16, 3)
53
+ ptr.write_array_of_int16([1, 2, 3])
54
+ fmv = Fiddle::MemoryView.new(ptr)
55
+
56
+ assert_int_array fmv
57
+ end
58
+
51
59
  class TestConverter < self
52
60
  def test_ndav_from_ffi_memory_pointer
53
61
  ptr = ::FFI::MemoryPointer.new(:int16, 3)
data/test/test_ndav.rb CHANGED
@@ -35,4 +35,12 @@ class TestNDAV < TestBase
35
35
 
36
36
  assert_int_array ndav
37
37
  end
38
+
39
+ def test_column_major
40
+ str = [1, 2, 3,
41
+ 4, 5, 6].pack("s*")
42
+ ndav = NDAV.from_string(str, format: "s", shape: [2, 3], strides: [2, 4])
43
+
44
+ assert_true ndav.column_major_contiguous?
45
+ end
38
46
  end
@@ -0,0 +1,79 @@
1
+ require_relative "helper"
2
+
3
+ class TestRactor < TestBase
4
+ ractor
5
+
6
+ def test_ractor
7
+ ractor = Ractor.new {
8
+ Ractor.receive
9
+ }
10
+ ndav = NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
11
+ ractor << ndav
12
+ result = ractor.value
13
+
14
+ assert_not_same ndav, result
15
+ assert_int_array result
16
+ end
17
+
18
+ def test_ractor_from_string
19
+ ractor = Ractor.new {
20
+ Ractor.receive
21
+ }
22
+ ndav = NDAV.from_string([1, 2, 3].pack("s*"), format: "s")
23
+ ractor << ndav
24
+ result = ractor.value
25
+
26
+ assert_int_array result
27
+ end
28
+
29
+ def test_ractor_share
30
+ ractor = Ractor.new {
31
+ Ractor.receive
32
+ }
33
+ ndav = NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
34
+ Ractor.make_shareable(ndav)
35
+ ractor << ndav
36
+ result = ractor.value
37
+
38
+ assert_same ndav, result
39
+ assert_int_array result
40
+ end
41
+
42
+ def test_port
43
+ ractor = Ractor.new {
44
+ ndav, port = Ractor.receive
45
+ port << ndav
46
+ }
47
+ ndav = NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
48
+ port = Ractor::Port.new
49
+ ractor << [ndav, port]
50
+ result = port.receive
51
+
52
+ assert_not_same ndav, result
53
+ assert_int_array result
54
+ end
55
+
56
+ def test_port_share
57
+ ractor = Ractor.new {
58
+ ndav, port = Ractor.receive
59
+ port << ndav
60
+ }
61
+ ndav = NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
62
+ Ractor.make_shareable(ndav)
63
+ port = Ractor::Port.new
64
+ ractor << [ndav, port]
65
+ result = port.receive
66
+
67
+ assert_same ndav, result
68
+ assert_int_array result
69
+ end
70
+
71
+ def test_ractor_not_initializable
72
+ ractor = Ractor.new {
73
+ NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
74
+ }
75
+ assert_raise Ractor::RemoteError do
76
+ ractor.value
77
+ end
78
+ end
79
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ndav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kitaiti Makoto
@@ -163,6 +163,20 @@ dependencies:
163
163
  - - ">="
164
164
  - !ruby/object:Gem::Version
165
165
  version: '0'
166
+ - !ruby/object:Gem::Dependency
167
+ name: commonmarker
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
166
180
  executables: []
167
181
  extensions:
168
182
  - ext/extconf.rb
@@ -173,15 +187,15 @@ files:
173
187
  - ".yardopts"
174
188
  - Gemfile
175
189
  - LICENSE.txt
176
- - NDAV.png
177
190
  - README.md
178
191
  - Rakefile
179
192
  - ext/extconf.rb
180
193
  - ext/ndav.c
181
- - ext/ndav.h
182
194
  - lib/ndav.rb
183
195
  - lib/ndav/converter.rb
184
196
  - lib/ndav/ffi.rb
197
+ - lib/ndav/flags.rb
198
+ - lib/ndav/memory_viewable.rb
185
199
  - ndav.gemspec
186
200
  - test/helper.rb
187
201
  - test/test_converter.rb
@@ -189,6 +203,7 @@ files:
189
203
  - test/test_ffi.rb
190
204
  - test/test_ndav.rb
191
205
  - test/test_package.rb
206
+ - test/test_ractor.rb
192
207
  homepage: https://kitaitimakoto.gitlab.io/ndav
193
208
  licenses:
194
209
  - BSD-2-Clause
@@ -208,7 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
208
223
  - !ruby/object:Gem::Version
209
224
  version: '0'
210
225
  requirements: []
211
- rubygems_version: 4.0.6
226
+ rubygems_version: 4.0.16
212
227
  specification_version: 4
213
228
  summary: N-Dimensional Array View
214
229
  test_files: []
data/NDAV.png DELETED
Binary file
data/ext/ndav.h DELETED
@@ -1,2 +0,0 @@
1
- #include <ruby.h>
2
- #include <ruby/memory_view.h>