ndav 0.0.4 → 0.0.5

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: '0648fef27ca47ec51a45cacb90330e5fb2de53071d4223604752c04746ad9c2d'
4
+ data.tar.gz: 4af1dacda9bfa1258552bc271d773136b06ea414cce61c75c194c590f1361850
5
5
  SHA512:
6
- metadata.gz: 6fe639a15fd780e5add9dcf124f3222214f6dd82cef28e4c16589f0f9dae936dda08e378ed28a3fa9bb810933250caf48bad2a18885a6261685451b3faf14bf0
7
- data.tar.gz: 3b3769ce7d12622bd231d0f4bffeb590b631a4c6be42984e2c998c49b64d2c24499bf922744d11b02c27dd308735c8f29d3385c70d5f3753509ce3bd70d4415f
6
+ metadata.gz: b81a17db0cc3814963b47e590e2f3e021e86792326a98ad1f246df8f887c4f1e49aec42808c82aeceb522e28f05f7dac9d3a6eb65417fbfc9ff785cb75284723
7
+ data.tar.gz: 798ed5c4f2f84fa73b262af460e0054454a78d6f8a9eeacbb191f0b866c6c4e98ae72ed296af23b7a67d98d6894a988cc22dfc23484267d1773bd91ed7043a63
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
  --------
@@ -70,7 +70,7 @@ USAGE
70
70
  ndav = NDAV.from_numo_narray(numo) # => NDAV
71
71
 
72
72
  include NDAV::Converter
73
- numo = NumoNArray(ndav) #=> Numo::SFloat
73
+ numo = NumoNArray(ndav) # => Numo::SFloat
74
74
  ndav = NDAV(numo) # => NDAV
75
75
 
76
76
  For `Torch::Tensor` and `OnnxRuntime::OrtValue`, you can do the same operation, therefore you may convert them to each other, like this:
@@ -100,7 +100,7 @@ NDAV can be initialized *directly* from libraries which export [MemoryView][] su
100
100
  arrow = Arrow::Int16Array.new([1, 2, 3])
101
101
  ndav = NDAV.new(arrow)
102
102
 
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:
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:
104
104
 
105
105
  waveform, sample_rate = TorchAudio.load("path/to/audio.wav")
106
106
  samples = waveform.to_ndav
@@ -128,12 +128,18 @@ BRIDGES
128
128
 
129
129
  There are some bridges using NDAV:
130
130
 
131
- * [ndav/ffi][]: [`FFI::MemoryPointer`][FFI Pointer], [`FFI::Pointer`][FFI Pointer] <-> `NDAV`
131
+ * {file:ndav/ffi ndav/ffi}: [`FFI::MemoryPointer`][FFI Pointer], [`FFI::Pointer`][FFI Pointer] <-> `NDAV`
132
132
  * [ndav-numo-narray][]: [`Numo::NArray`][Numo::NArray] <-> `NDAV`
133
133
  * [ndav-ort_value][]: [`OnnxRuntime::OrtValue`][ONNX Runtime Ruby] <-> `NDAV`
134
134
  * [ndav-torch-tensor][]: [`Torch::Tensor`][Torch.rb] <-> `NDAV`
135
135
  * [onnxruntime-torch-tensor][]: [`Torch::Tensor`][Torch.rb] <-> [`OnnxRuntime::OrtValue`][ONNX Runtime Ruby] via `NDAV`
136
136
 
137
+ APPLICATIONS AND LIBRARIES WHICH USE NDAV
138
+ -----------------------------------------
139
+
140
+ * [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.
141
+ * [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.
142
+
137
143
  CREATING BRIDGES
138
144
  ----------------
139
145
 
@@ -141,8 +147,8 @@ Refer to existing bridge implementations listed above to create your bridge.
141
147
 
142
148
  The points are:
143
149
 
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
150
+ * Implement `FromNDAV#from_ndav`, `ToNDAV#to_ndav`, {NDAV.register register} them, and `NDAV.from_your_data` and `NDAV#to_your_data` are automatically derived
151
+ * 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
152
  * 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
153
 
148
154
  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 +163,7 @@ If [MemoryView][] gets popular enough in the Ruby ecosystem, this library will e
157
163
  LICENSE
158
164
  -------
159
165
 
160
- BSD-2-Clause license. See LICENSE.txt file.
166
+ BSD-2-Clause license. See {file:LICENSE.txt} file.
161
167
 
162
168
  [MemoryView]: https://docs.ruby-lang.org/en/master/contributing/memory_view_md.html
163
169
  [Numo::NArray]: https://ruby-numo.github.io/narray/
@@ -165,9 +171,13 @@ BSD-2-Clause license. See LICENSE.txt file.
165
171
  [ONNX Runtime Ruby]: https://github.com/ankane/onnxruntime-ruby
166
172
  [Red Arrow]: https://github.com/apache/arrow/tree/main/ruby
167
173
  [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
174
+ [ndav/ffi]: https://gitlab.com/KitaitiMakoto/ndav/-/blob/main/lib/ndav/ffi.rb
169
175
  [FFI Pointer]: https://github.com/ffi/ffi/wiki/Pointers
176
+ [whispercpp]: https://github.com/ggml-org/whisper.cpp/tree/master/bindings/ruby
170
177
  [ndav-numo-narray]: https://gitlab.com/KitaitiMakoto/ndav-numo-narray
171
178
  [ndav-ort_value]: https://gitlab.com/KitaitiMakoto/ndav-ort_value
172
179
  [ndav-torch-tensor]: https://gitlab.com/KitaitiMakoto/ndav-torch-tensor
173
180
  [onnxruntime-torch-tensor]: https://gitlab.com/KitaitiMakoto/onnxruntime-torch-tensor
181
+ [GTCRN]: https://gitlab.com/KitaitiMakoto/gtcrn
182
+ [TorchAudio]: https://github.com/ankane/torchaudio-ruby
183
+ [Itak]: https://gitlab.com/KitaitiMakoto/itak
data/ext/ndav.c CHANGED
@@ -1,22 +1,34 @@
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_data;
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;
20
32
  }
21
33
 
22
34
  static bool
@@ -25,24 +37,38 @@ ndav_get_memory_view(const VALUE ndav, rb_memory_view_t *view, int flags)
25
37
  bool row_major_requested = (flags & RUBY_MEMORY_VIEW_ROW_MAJOR) == RUBY_MEMORY_VIEW_ROW_MAJOR;
26
38
  bool column_major_requested = (flags & RUBY_MEMORY_VIEW_COLUMN_MAJOR) == RUBY_MEMORY_VIEW_COLUMN_MAJOR;
27
39
  bool indirect_requested = (flags & RUBY_MEMORY_VIEW_INDIRECT) == RUBY_MEMORY_VIEW_INDIRECT;
28
- if ((column_major_requested && !row_major_requested) || indirect_requested) {
40
+ VALUE desc = rb_funcall(ndav, id_ndav_validated_descriptor, 1, INT2NUM(flags));
41
+ if (!RB_TYPE_P(desc, T_HASH)) {
42
+ rb_warn("descriptor not Hash");
43
+ return false;
44
+ }
45
+ bool row_major_contiguous = rb_hash_aref(desc, sym_row_major_contiguous_p);
46
+ bool column_major_contiguous = rb_hash_aref(desc, sym_column_major_contiguous_p);
47
+ if (row_major_requested && column_major_requested) { // row-major OR column-major requested
48
+ if (!row_major_contiguous && !column_major_contiguous) {
49
+ return false;
50
+ }
51
+ }
52
+ // Currently, non-contiguous array not supported
53
+ else if (row_major_requested && !row_major_contiguous) {
54
+ return false;
55
+ }
56
+ // Currently, non-contiguous array not supported
57
+ else if (column_major_requested && !column_major_contiguous) {
29
58
  return false;
30
59
  }
31
60
  bool writable_requested = (flags & RUBY_MEMORY_VIEW_WRITABLE) == RUBY_MEMORY_VIEW_WRITABLE;
32
61
  // TODO: Handle flags
33
62
 
34
- VALUE readonly = rb_iv_get(ndav, "@readonly");
35
- if (NIL_P(readonly)) {
36
- if (writable_requested) {
37
- rb_warn("not writable");
38
- return false;
39
- }
40
- view->readonly = true;
41
- } else {
42
- view->readonly = RTEST(readonly);
63
+ VALUE readonly_v = rb_hash_aref(desc, sym_readonly_p);
64
+ bool readonly = NIL_P(readonly_v) || RTEST(readonly_v);
65
+ if (writable_requested && readonly) {
66
+ rb_warn("not writable");
67
+ return false;
43
68
  }
69
+ view->readonly = readonly;
44
70
  view->obj = ndav;
45
- VALUE val = rb_funcall(ndav, id_to_s, 0);
71
+ VALUE val = rb_hash_aref(desc, sym_data);
46
72
  char *data = StringValuePtr(val);
47
73
  view->data = data;
48
74
  // TODO: Commonalize
@@ -50,59 +76,91 @@ ndav_get_memory_view(const VALUE ndav, rb_memory_view_t *view, int flags)
50
76
  // * Use TypedData?
51
77
  // * If so, calling attr readers at Ruby layer has performance disadvantage.
52
78
  // * 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");
79
+ VALUE item_size = rb_hash_aref(desc, sym_item_size);
54
80
  view->item_size = NUM2SSIZET(item_size);
55
- VALUE byte_size = rb_iv_get(ndav, "@byte_size");
81
+ VALUE byte_size = rb_hash_aref(desc, sym_byte_size);
56
82
  view->byte_size = NUM2SSIZET(byte_size);
57
- VALUE ndim = rb_iv_get(ndav, "@ndim");
83
+ VALUE ndim = rb_hash_aref(desc, sym_ndim);
58
84
  view->ndim = NUM2SSIZET(ndim);
59
- VALUE format = rb_iv_get(ndav, "@format");
85
+ VALUE format = rb_hash_aref(desc, sym_format);
60
86
  view->format = StringValueCStr(format);
61
87
 
62
- int state;
63
-
64
88
  // TODO: Commonalize
65
- VALUE shape = rb_iv_get(ndav, "@shape");
89
+ VALUE shape = rb_hash_aref(desc, sym_shape);
66
90
  if (!RB_TYPE_P(shape, T_ARRAY)) {
67
91
  rb_warn("@shape is not an array");
68
92
  return false;
69
93
  }
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);
94
+ ssize_t *view_shape = (ssize_t *)malloc(sizeof(ssize_t) * view->ndim);
95
+ if (!view_shape) {
96
+ return false;
97
+ }
98
+ if (!fill_size_array(shape, view_shape, view->ndim)) {
99
+ free((void *)view_shape);
80
100
  return false;
81
101
  }
82
102
  view->shape = view_shape;
83
103
 
84
- VALUE strides = rb_iv_get(ndav, "@strides");
104
+ VALUE strides = rb_hash_aref(desc, sym_strides);
85
105
  if (!RB_TYPE_P(strides, T_ARRAY)) {
86
106
  rb_warn("@strides is not an array");
87
- xfree((void *)view->shape);
107
+ free((void *)view->shape);
88
108
  return false;
89
109
  }
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);
110
+ ssize_t *view_strides = (ssize_t *)malloc(sizeof(ssize_t) * view->ndim);
111
+ if (!view_strides) {
112
+ free((void *)view->shape);
113
+ return false;
114
+ }
115
+ if (!fill_size_array(strides, view_strides, view->ndim)) {
116
+ free((void *)view->shape);
117
+ free((void *)view_strides);
101
118
  return false;
102
119
  }
103
120
  view->strides = view_strides;
104
121
 
105
- view->sub_offsets = NULL;
122
+ VALUE sub_offsets = rb_hash_aref(desc, sym_sub_offsets);
123
+ if (NIL_P(sub_offsets)) {
124
+ if (indirect_requested) {
125
+ rb_warn("indirect requested but sub_offsets is NULL");
126
+ free((void *)view->shape);
127
+ free((void *)view->strides);
128
+ return false;
129
+ }
130
+ view->sub_offsets = NULL;
131
+ } else if (!RB_TYPE_P(sub_offsets, T_ARRAY)) {
132
+ rb_warn("sub_offsets is not an array");
133
+ free((void *)view->shape);
134
+ free((void *)view->strides);
135
+ return false;
136
+ } else {
137
+ ssize_t *view_sub_offsets = (ssize_t *)malloc(sizeof(ssize_t) * view->ndim);
138
+ if (!view_sub_offsets) {
139
+ free((void *)view->shape);
140
+ free((void *)view->strides);
141
+ return false;
142
+ }
143
+ if (!fill_size_array(sub_offsets, view_sub_offsets, view->ndim)) {
144
+ free((void *)view->shape);
145
+ free((void *)view->strides);
146
+ free((void *)view_sub_offsets);
147
+ return false;
148
+ }
149
+ view->sub_offsets = view_sub_offsets;
150
+ }
151
+
152
+ private_data_t *private_data = malloc(sizeof(private_data_t));
153
+ if (!private_data) {
154
+ free((void *)view->shape);
155
+ free((void *)view->strides);
156
+ free((void *)view->sub_offsets);
157
+ rb_warn("failed to alloc private_data");
158
+ return false;
159
+ }
160
+ private_data->descriptor = Qnil;
161
+ rb_gc_register_address(&private_data->descriptor);
162
+ private_data->descriptor = desc;
163
+ view->private_data = private_data;
106
164
 
107
165
  return true;
108
166
  }
@@ -111,13 +169,23 @@ static bool
111
169
  ndav_release_memory_view(const VALUE ndav, rb_memory_view_t *view)
112
170
  {
113
171
  if (view->shape) {
114
- xfree((void *)view->shape);
172
+ free((void *)view->shape);
115
173
  view->shape = NULL;
116
174
  }
117
175
  if (view->strides) {
118
- xfree((void *)view->strides);
176
+ free((void *)view->strides);
119
177
  view->strides = NULL;
120
178
  }
179
+ if (view->sub_offsets) {
180
+ free((void *)view->sub_offsets);
181
+ view->sub_offsets = NULL;
182
+ }
183
+ if (view->private_data) {
184
+ private_data_t *private_data = (private_data_t *)view->private_data;
185
+ rb_gc_unregister_address(&private_data->descriptor);
186
+ free(private_data);
187
+ view->private_data = NULL;
188
+ }
121
189
 
122
190
  return true;
123
191
  }
@@ -125,8 +193,8 @@ ndav_release_memory_view(const VALUE ndav, rb_memory_view_t *view)
125
193
  static bool
126
194
  ndav_memory_view_available_p(const VALUE obj)
127
195
  {
128
- VALUE fmv = rb_iv_get(obj, "@fmv");
129
- return !NIL_P(fmv);
196
+ VALUE descriptor = rb_funcall(obj, id_ndav_validated_descriptor, 0);
197
+ return RTEST(descriptor);
130
198
  }
131
199
 
132
200
  const struct rb_memory_view_entry ndav_view_entry = {
@@ -135,11 +203,34 @@ const struct rb_memory_view_entry ndav_view_entry = {
135
203
  ndav_memory_view_available_p
136
204
  };
137
205
 
206
+ static VALUE
207
+ ndav_memory_viewable_s_register(VALUE mod, VALUE klass)
208
+ {
209
+ if (!rb_memory_view_register(klass, &ndav_view_entry)) {
210
+ // error message output in rb_memory_view_register()
211
+ rb_raise(rb_eArgError, "");
212
+ }
213
+
214
+ return Qtrue;
215
+ }
216
+
138
217
  void
139
218
  Init_ndav(void)
140
219
  {
141
- id_to_s = rb_intern("to_s");
220
+ id_ndav_validated_descriptor = rb_intern("ndav_validated_descriptor");
221
+ sym_data = ID2SYM(rb_intern("data"));
222
+ sym_shape = ID2SYM(rb_intern("shape"));
223
+ sym_strides = ID2SYM(rb_intern("strides"));
224
+ sym_format = ID2SYM(rb_intern("format"));
225
+ sym_byte_size = ID2SYM(rb_intern("byte_size"));
226
+ sym_readonly_p = ID2SYM(rb_intern("readonly?"));
227
+ sym_sub_offsets = ID2SYM(rb_intern("sub_offsets"));
228
+ sym_ndim = ID2SYM(rb_intern("ndim"));
229
+ sym_item_size = ID2SYM(rb_intern("item_size"));
230
+ sym_row_major_contiguous_p = ID2SYM(rb_intern("row_major_contiguous?"));
231
+ sym_column_major_contiguous_p = ID2SYM(rb_intern("column_major_contiguous?"));
142
232
 
143
233
  VALUE cNDAV = rb_define_class("NDAV", rb_cObject);
144
- rb_memory_view_register(cNDAV, &ndav_view_entry);
234
+ VALUE mMemoryViewable = rb_define_module_under(cNDAV, "MemoryViewable");
235
+ rb_define_singleton_method(mMemoryViewable, "register", ndav_memory_viewable_s_register, 1);
145
236
  }
@@ -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,47 @@
1
+ class NDAV
2
+ module MemoryViewable
3
+ class << self
4
+ def included(base)
5
+ ::NDAV::MemoryViewable.register base
6
+ end
7
+ end
8
+
9
+ private
10
+
11
+ # Overwrite this method or define each ndav_xxx method
12
+ def ndav_descriptor(simple: true, writable: false, format: false, multi_dimensional: false, strides: false, row_major: false, column_major: false, any_contiguous: false, indirect: false)
13
+ {
14
+ data: ndav_data,
15
+ shape: ndav_shape,
16
+ strides: ndav_strides,
17
+ format: ndav_format,
18
+ byte_size: ndav_byte_size,
19
+ readonly?: ndav_readonly?,
20
+ sub_offsets: ndav_sub_offsets
21
+ }
22
+ end
23
+
24
+ def ndav_validated_descriptor(flags = ::NDAV::Flags::FLAGS[:simple])
25
+ desc = ndav_descriptor(**NDAV::Flags.decode(flags)).to_h
26
+ unless desc.kind_of? Hash
27
+ warn "descriptor not Hash"
28
+ return false
29
+ end
30
+ [:data, :shape, :strides, :format, :byte_size, :readonly?].each do |key|
31
+ unless desc.key? key
32
+ warn ":#{key} not in descriptor"
33
+ return false
34
+ end
35
+ end
36
+ desc.merge(
37
+ ndim: desc[:shape].length,
38
+ item_size: NDAV::ITEM_SIZES[desc[:format]],
39
+ row_major_contiguous?: ::NDAV.row_major_contiguous?(**desc),
40
+ column_major_contiguous?: ::NDAV.column_major_contiguous?(**desc)
41
+ )
42
+ rescue => err
43
+ warn err
44
+ return false
45
+ end
46
+ end
47
+ 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.5"
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.5
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>