pnmatrix 2.0.0 → 2.0.1
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 +4 -4
- data/ext/nmatrix/nmatrix.h +8 -0
- data/ext/nmatrix/ruby_nmatrix.c +8 -2
- data/ext/nmatrix/storage/dense/dense.cpp +35 -12
- data/ext/nmatrix/storage/list/list.cpp +3 -0
- data/ext/nmatrix/storage/yale/class.h +71 -16
- data/ext/nmatrix/storage/yale/iterators/row.h +5 -5
- data/ext/nmatrix/storage/yale/yale.cpp +23 -4
- data/lib/nmatrix/mkmf.rb +1 -1
- data/lib/nmatrix/nmatrix.rb +1 -1
- data/lib/nmatrix/version.rb +1 -2
- data/spec/io_spec.rb +3 -1
- data/spec/lapack_core_spec.rb +13 -8
- data/spec/math_spec.rb +33 -13
- data/spec/stress_test_spec.rb +163 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a13074d0b3e5e2bbee2a456f42fe5d5d7a4b4e581e2ae42e149d74f4a1452211
|
|
4
|
+
data.tar.gz: 917446ac40ebe45b13a62c6c0e1e927227beee199a411e2c0cc1fa83f5e9f1bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 697596b71a0d302a785d4fe503676fe89392b60e84354fba5151b873f2bbf570e11f6086972c30bdf610eb9a2621387b5bf469c82280e8586888241773a3668f
|
|
7
|
+
data.tar.gz: 2a42bf5f5e4c97748e6d44aa631086e94c18bdb0b6f56503ce236d97a57cb8da0a272a016686b88d656f67f86d82124f9bef5bb7fbbfe39b91deefd6c657810d
|
data/ext/nmatrix/nmatrix.h
CHANGED
|
@@ -376,6 +376,14 @@ NM_DEF_STRUCT_POST(NM_GC_HOLDER); // };
|
|
|
376
376
|
// enum for a list of possible storage types.
|
|
377
377
|
#define NM_STYPE(val) (NM_STRUCT(val)->stype)
|
|
378
378
|
|
|
379
|
+
/*
|
|
380
|
+
* Object dtype storage keeps Ruby VALUEs in malloc-backed arrays/lists, not in
|
|
381
|
+
* Ruby object fields. After mutating that native storage, make the wrapper
|
|
382
|
+
* object write-barrier-unprotected so minor GC runs nm_mark and sees newly
|
|
383
|
+
* assigned VALUEs before they can be collected.
|
|
384
|
+
*/
|
|
385
|
+
#define NM_OBJECT_STORAGE_WB_UNPROTECT(val) RB_OBJ_WB_UNPROTECT(val)
|
|
386
|
+
|
|
379
387
|
// Get the shape of the ith dimension (int)
|
|
380
388
|
#define NM_SHAPE(val,i) (NM_STORAGE(val)->shape[(i)])
|
|
381
389
|
|
data/ext/nmatrix/ruby_nmatrix.c
CHANGED
|
@@ -412,8 +412,14 @@ static VALUE nm_alloc(VALUE klass) {
|
|
|
412
412
|
mat->stype = nm::DENSE_STORE;
|
|
413
413
|
mat->storage = NULL;
|
|
414
414
|
|
|
415
|
-
|
|
416
|
-
|
|
415
|
+
/*
|
|
416
|
+
* Newly allocated NMatrix wrappers do not have storage yet, but nm_mark is
|
|
417
|
+
* explicitly null-storage safe. Installing it here is required for object
|
|
418
|
+
* dtype matrices after initialization: otherwise Ruby never asks the matrix
|
|
419
|
+
* to mark VALUEs stored in native dense/list/Yale memory, so a later GC can
|
|
420
|
+
* reclaim objects that were assigned into the matrix.
|
|
421
|
+
*/
|
|
422
|
+
return Data_Wrap_Struct(klass, nm_mark, nm_delete, mat);
|
|
417
423
|
}
|
|
418
424
|
|
|
419
425
|
/*
|
|
@@ -102,17 +102,21 @@ namespace nm { namespace dense_storage {
|
|
|
102
102
|
* Recursive function, sets multiple values in a matrix from a single source value. Same basic pattern as slice_copy.
|
|
103
103
|
*/
|
|
104
104
|
template <typename D>
|
|
105
|
-
static void slice_set(DENSE_STORAGE* dest, size_t* lengths, size_t pdest, size_t rank, D* const v, size_t v_size, size_t& v_offset) {
|
|
105
|
+
static void slice_set(VALUE owner, DENSE_STORAGE* dest, size_t* lengths, size_t pdest, size_t rank, D* const v, size_t v_size, size_t& v_offset) {
|
|
106
106
|
if (dest->dim - rank > 1) {
|
|
107
107
|
for (size_t i = 0; i < lengths[rank]; ++i) {
|
|
108
|
-
slice_set<D>(dest, lengths, pdest + dest->stride[rank] * i, rank + 1, v, v_size, v_offset);
|
|
108
|
+
slice_set<D>(owner, dest, lengths, pdest + dest->stride[rank] * i, rank + 1, v, v_size, v_offset);
|
|
109
109
|
}
|
|
110
110
|
} else {
|
|
111
111
|
for (size_t p = 0; p < lengths[rank]; ++p, ++v_offset) {
|
|
112
112
|
if (v_offset >= v_size) v_offset %= v_size;
|
|
113
113
|
|
|
114
114
|
D* elem = reinterpret_cast<D*>(dest->elements);
|
|
115
|
-
|
|
115
|
+
if (dest->dtype == nm::RUBYOBJ) {
|
|
116
|
+
RB_OBJ_WRITE(owner, reinterpret_cast<VALUE*>(elem) + p + pdest, reinterpret_cast<VALUE*>(v)[v_offset]);
|
|
117
|
+
} else {
|
|
118
|
+
elem[p + pdest] = v[v_offset];
|
|
119
|
+
}
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
122
|
}
|
|
@@ -127,6 +131,9 @@ namespace nm { namespace dense_storage {
|
|
|
127
131
|
NM_CONSERVATIVE(nm_register_value(&right));
|
|
128
132
|
|
|
129
133
|
DENSE_STORAGE* s = NM_STORAGE_DENSE(left);
|
|
134
|
+
if (s->dtype == nm::RUBYOBJ) {
|
|
135
|
+
NM_OBJECT_STORAGE_WB_UNPROTECT(left);
|
|
136
|
+
}
|
|
130
137
|
|
|
131
138
|
std::pair<NMATRIX*,bool> nm_and_free =
|
|
132
139
|
interpret_arg_as_dense_nmatrix(right, s->dtype);
|
|
@@ -158,10 +165,15 @@ namespace nm { namespace dense_storage {
|
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
if (slice->single) {
|
|
161
|
-
|
|
168
|
+
size_t pos = nm_dense_storage_pos(s, slice->coords);
|
|
169
|
+
if (s->dtype == nm::RUBYOBJ) {
|
|
170
|
+
RB_OBJ_WRITE(left, reinterpret_cast<VALUE*>(s->elements) + pos, *reinterpret_cast<VALUE*>(v));
|
|
171
|
+
} else {
|
|
172
|
+
reinterpret_cast<D*>(s->elements)[pos] = *v;
|
|
173
|
+
}
|
|
162
174
|
} else {
|
|
163
175
|
size_t v_offset = 0;
|
|
164
|
-
slice_set(s, slice->lengths, nm_dense_storage_pos(s, slice->coords), 0, v, v_size, v_offset);
|
|
176
|
+
slice_set(left, s, slice->lengths, nm_dense_storage_pos(s, slice->coords), 0, v, v_size, v_offset);
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
// Only free v if it was allocated in this function.
|
|
@@ -311,14 +323,25 @@ void nm_dense_storage_mark(STORAGE* storage_base) {
|
|
|
311
323
|
DENSE_STORAGE* storage = (DENSE_STORAGE*)storage_base;
|
|
312
324
|
|
|
313
325
|
if (storage && storage->dtype == nm::RUBYOBJ) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
326
|
+
/*
|
|
327
|
+
* Dense slice references share the source storage's elements pointer and
|
|
328
|
+
* may start at a non-zero offset. Marking only the reference's element
|
|
329
|
+
* count from offset zero can miss Ruby objects that are visible through
|
|
330
|
+
* the reference. Mark the backing source storage so every object that a
|
|
331
|
+
* live reference can expose stays alive.
|
|
332
|
+
*/
|
|
333
|
+
DENSE_STORAGE* src = reinterpret_cast<DENSE_STORAGE*>(storage->src);
|
|
334
|
+
|
|
335
|
+
size_t count = src ? nm_storage_count_max_elements(src) : 0;
|
|
336
|
+
if (src && src->elements && count > 0) {
|
|
337
|
+
VALUE* els = reinterpret_cast<VALUE*>(src->elements);
|
|
338
|
+
/*
|
|
339
|
+
* This storage holds exact Ruby VALUEs, not arbitrary machine words.
|
|
340
|
+
* Mark each slot explicitly so objects assigned into malloc-backed
|
|
341
|
+
* matrix memory are kept alive just like Ruby object fields would be.
|
|
342
|
+
*/
|
|
343
|
+
for (size_t i = 0; i < count; ++i) rb_gc_mark(els[i]);
|
|
318
344
|
}
|
|
319
|
-
//for (size_t index = nm_storage_count_max_elements(storage); index-- > 0;) {
|
|
320
|
-
// rb_gc_mark(els[index]);
|
|
321
|
-
//}
|
|
322
345
|
}
|
|
323
346
|
}
|
|
324
347
|
|
|
@@ -538,6 +538,9 @@ void set(VALUE left, SLICE* slice, VALUE right) {
|
|
|
538
538
|
NM_CONSERVATIVE(nm_register_value(&left));
|
|
539
539
|
NM_CONSERVATIVE(nm_register_value(&right));
|
|
540
540
|
LIST_STORAGE* s = NM_STORAGE_LIST(left);
|
|
541
|
+
if (s->dtype == nm::RUBYOBJ) {
|
|
542
|
+
NM_OBJECT_STORAGE_WB_UNPROTECT(left);
|
|
543
|
+
}
|
|
541
544
|
|
|
542
545
|
std::pair<NMATRIX*,bool> nm_and_free =
|
|
543
546
|
interpret_arg_as_dense_nmatrix(right, NM_DTYPE(left));
|
|
@@ -50,7 +50,18 @@ public:
|
|
|
50
50
|
: s(reinterpret_cast<YALE_STORAGE*>(storage->src)),
|
|
51
51
|
slice(storage != storage->src),
|
|
52
52
|
slice_shape(storage->shape),
|
|
53
|
-
slice_offset(storage->offset)
|
|
53
|
+
slice_offset(storage->offset),
|
|
54
|
+
owner(Qnil)
|
|
55
|
+
{
|
|
56
|
+
nm_yale_storage_register(storage->src);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
YaleStorage(const YALE_STORAGE* storage, VALUE owner_)
|
|
60
|
+
: s(reinterpret_cast<YALE_STORAGE*>(storage->src)),
|
|
61
|
+
slice(storage != storage->src),
|
|
62
|
+
slice_shape(storage->shape),
|
|
63
|
+
slice_offset(storage->offset),
|
|
64
|
+
owner(owner_)
|
|
54
65
|
{
|
|
55
66
|
nm_yale_storage_register(storage->src);
|
|
56
67
|
}
|
|
@@ -59,7 +70,8 @@ public:
|
|
|
59
70
|
: s(reinterpret_cast<YALE_STORAGE*>(storage->src)),
|
|
60
71
|
slice(storage != storage->src),
|
|
61
72
|
slice_shape(storage->shape),
|
|
62
|
-
slice_offset(storage->offset)
|
|
73
|
+
slice_offset(storage->offset),
|
|
74
|
+
owner(Qnil)
|
|
63
75
|
{
|
|
64
76
|
nm_yale_storage_register(reinterpret_cast<STORAGE*>(storage->src));
|
|
65
77
|
}
|
|
@@ -96,6 +108,17 @@ public:
|
|
|
96
108
|
inline const D& a(size_t p) const { return a_p()[p]; }
|
|
97
109
|
inline D& a(size_t p) { return a_p()[p]; }
|
|
98
110
|
|
|
111
|
+
// Object matrices store Ruby VALUEs in native memory, so replacing a value must
|
|
112
|
+
// go through Ruby's write barrier. Without it, generational GC can collect a
|
|
113
|
+
// newly assigned object before the matrix is marked again.
|
|
114
|
+
inline void write_a(size_t p, const D& val) {
|
|
115
|
+
if (dtype() == nm::RUBYOBJ && owner != Qnil) {
|
|
116
|
+
RB_OBJ_WRITE(owner, reinterpret_cast<VALUE*>(s->a) + p, reinterpret_cast<const VALUE*>(&val)[0]);
|
|
117
|
+
} else {
|
|
118
|
+
a(p) = val;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
99
122
|
bool real_row_empty(size_t i) const { return ija(i+1) - ija(i) == 0 ? true : false; }
|
|
100
123
|
|
|
101
124
|
inline size_t* shape_p() const { return slice_shape; }
|
|
@@ -467,7 +490,7 @@ public:
|
|
|
467
490
|
* A pseudo-insert operation, since the diagonal portion of the A array is constant size.
|
|
468
491
|
*/
|
|
469
492
|
stored_diagonal_iterator insert(stored_diagonal_iterator position, const D& val) {
|
|
470
|
-
|
|
493
|
+
write_a(position.p(), val);
|
|
471
494
|
return position;
|
|
472
495
|
}
|
|
473
496
|
|
|
@@ -694,24 +717,37 @@ public:
|
|
|
694
717
|
|
|
695
718
|
E* ns_a = reinterpret_cast<E*>(ns.a);
|
|
696
719
|
size_t sz = shape(0) + 1; // current used size of ns
|
|
697
|
-
|
|
720
|
+
E converted = val;
|
|
721
|
+
if (ns.dtype == nm::RUBYOBJ) {
|
|
722
|
+
nm_register_value(reinterpret_cast<VALUE*>(&converted));
|
|
723
|
+
}
|
|
698
724
|
|
|
699
725
|
// FIXME: If diagonals line up, it's probably faster to do this with stored diagonal and stored non-diagonal iterators
|
|
700
726
|
for (const_row_iterator it = cribegin(); it != criend(); ++it) {
|
|
701
727
|
for (auto jt = it.begin(); !jt.end(); ++jt) {
|
|
702
728
|
if (it.i() == jt.j()) {
|
|
703
|
-
if (Yield)
|
|
704
|
-
else
|
|
729
|
+
if (Yield) converted = E(rb_yield(~jt));
|
|
730
|
+
else converted = static_cast<E>(*jt);
|
|
731
|
+
if (ns.dtype == nm::RUBYOBJ) {
|
|
732
|
+
nm_register_value(reinterpret_cast<VALUE*>(&converted));
|
|
733
|
+
}
|
|
734
|
+
ns_a[it.i()] = converted;
|
|
705
735
|
} else if (*jt != const_default_obj()) {
|
|
706
|
-
if (Yield)
|
|
707
|
-
else
|
|
736
|
+
if (Yield) converted = E(rb_yield(~jt));
|
|
737
|
+
else converted = static_cast<E>(*jt);
|
|
738
|
+
if (ns.dtype == nm::RUBYOBJ) {
|
|
739
|
+
nm_register_value(reinterpret_cast<VALUE*>(&converted));
|
|
740
|
+
}
|
|
741
|
+
ns_a[sz] = converted;
|
|
708
742
|
ns.ija[sz] = jt.j();
|
|
709
743
|
++sz;
|
|
710
744
|
}
|
|
711
745
|
}
|
|
712
746
|
ns.ija[it.i()+1] = sz;
|
|
713
747
|
}
|
|
714
|
-
|
|
748
|
+
if (ns.dtype == nm::RUBYOBJ) {
|
|
749
|
+
nm_unregister_value(reinterpret_cast<VALUE*>(&converted));
|
|
750
|
+
}
|
|
715
751
|
|
|
716
752
|
//ns.ija[shape(0)] = sz; // indicate end of last row
|
|
717
753
|
ns.ndnz = sz - shape(0) - 1; // update ndnz count
|
|
@@ -751,14 +787,24 @@ public:
|
|
|
751
787
|
|
|
752
788
|
E* la = reinterpret_cast<E*>(lhs->a);
|
|
753
789
|
|
|
754
|
-
|
|
790
|
+
E converted;
|
|
791
|
+
if (lhs->dtype == nm::RUBYOBJ) {
|
|
792
|
+
nm_register_value(reinterpret_cast<VALUE*>(&converted));
|
|
793
|
+
}
|
|
755
794
|
for (size_t m = 0; m < size(); ++m) {
|
|
756
795
|
if (Yield) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
796
|
+
converted = E(rb_yield(nm::yale_storage::nm_rb_dereference(a(m))));
|
|
797
|
+
} else {
|
|
798
|
+
converted = static_cast<E>(a(m));
|
|
799
|
+
}
|
|
800
|
+
if (lhs->dtype == nm::RUBYOBJ) {
|
|
801
|
+
nm_register_value(reinterpret_cast<VALUE*>(&converted));
|
|
802
|
+
}
|
|
803
|
+
la[m] = converted;
|
|
804
|
+
}
|
|
805
|
+
if (lhs->dtype == nm::RUBYOBJ) {
|
|
806
|
+
nm_unregister_value(reinterpret_cast<VALUE*>(&converted));
|
|
760
807
|
}
|
|
761
|
-
nm_yale_storage_unregister(lhs);
|
|
762
808
|
|
|
763
809
|
}
|
|
764
810
|
|
|
@@ -995,10 +1041,18 @@ protected:
|
|
|
995
1041
|
if (v_offset >= v_size) v_offset %= v_size;
|
|
996
1042
|
|
|
997
1043
|
if (j + real_j == i + real_i) { // modify diagonal
|
|
998
|
-
|
|
1044
|
+
if (s->dtype == nm::RUBYOBJ && owner != Qnil) {
|
|
1045
|
+
RB_OBJ_WRITE(owner, reinterpret_cast<VALUE*>(new_a) + real_i + i, reinterpret_cast<const VALUE*>(v)[v_offset]);
|
|
1046
|
+
} else {
|
|
1047
|
+
new_a[real_i + i] = v[v_offset];
|
|
1048
|
+
}
|
|
999
1049
|
} else if (v[v_offset] != const_default_obj()) {
|
|
1000
1050
|
new_ija[q] = j + real_j;
|
|
1001
|
-
|
|
1051
|
+
if (s->dtype == nm::RUBYOBJ && owner != Qnil) {
|
|
1052
|
+
RB_OBJ_WRITE(owner, reinterpret_cast<VALUE*>(new_a) + q, reinterpret_cast<const VALUE*>(v)[v_offset]);
|
|
1053
|
+
} else {
|
|
1054
|
+
new_a[q] = v[v_offset];
|
|
1055
|
+
}
|
|
1002
1056
|
++q; // move on to next q location
|
|
1003
1057
|
}
|
|
1004
1058
|
|
|
@@ -1132,6 +1186,7 @@ protected:
|
|
|
1132
1186
|
bool slice;
|
|
1133
1187
|
size_t* slice_shape;
|
|
1134
1188
|
size_t* slice_offset;
|
|
1189
|
+
VALUE owner;
|
|
1135
1190
|
};
|
|
1136
1191
|
|
|
1137
1192
|
} // end of nm namespace
|
|
@@ -303,7 +303,7 @@ public:
|
|
|
303
303
|
while (!position.end() && position.j() < jj) ++position; // position is just a hint. (This loop ideally only has to happen once.)
|
|
304
304
|
|
|
305
305
|
if (!position.end() && position.j() == jj) {
|
|
306
|
-
|
|
306
|
+
y.write_a(position.p(), val); // replace existing
|
|
307
307
|
} else {
|
|
308
308
|
|
|
309
309
|
if (sz + 1 > y.capacity()) {
|
|
@@ -313,7 +313,7 @@ public:
|
|
|
313
313
|
y.update_real_row_sizes_from(real_i(), 1);
|
|
314
314
|
}
|
|
315
315
|
ija(position.p()) = jj + y.offset(1); // set column ID
|
|
316
|
-
|
|
316
|
+
y.write_a(position.p(), val);
|
|
317
317
|
adjust_length(1);
|
|
318
318
|
}
|
|
319
319
|
|
|
@@ -331,7 +331,7 @@ public:
|
|
|
331
331
|
*/
|
|
332
332
|
//template <typename = typename std::enable_if<!std::is_const<RefType>::value>::type>
|
|
333
333
|
void insert(size_t j, const D& val) {
|
|
334
|
-
if (j + y.offset(1) == real_i())
|
|
334
|
+
if (j + y.offset(1) == real_i()) y.write_a(real_i(), val);
|
|
335
335
|
else {
|
|
336
336
|
row_stored_nd_iterator jt = ndfind(j);
|
|
337
337
|
if (!jt.end() && jt.j() == j) {
|
|
@@ -404,10 +404,10 @@ public:
|
|
|
404
404
|
if (v_offset >= v_size) v_offset %= v_size; // reset v position.
|
|
405
405
|
|
|
406
406
|
if (jc + y.offset(1) == real_i()) {
|
|
407
|
-
y.
|
|
407
|
+
y.write_a(real_i(), v[v_offset]); // modify diagonal
|
|
408
408
|
} else if (v[v_offset] != y.const_default_obj()) {
|
|
409
409
|
y.ija(pp) = jc; // modify non-diagonal
|
|
410
|
-
y.
|
|
410
|
+
y.write_a(pp, v[v_offset]);
|
|
411
411
|
++pp;
|
|
412
412
|
}
|
|
413
413
|
}
|
|
@@ -369,7 +369,10 @@ YALE_STORAGE* ref(YALE_STORAGE* s, SLICE* slice) {
|
|
|
369
369
|
template <typename DType>
|
|
370
370
|
void set(VALUE left, SLICE* slice, VALUE right) {
|
|
371
371
|
YALE_STORAGE* storage = NM_STORAGE_YALE(left);
|
|
372
|
-
|
|
372
|
+
if (storage->dtype == nm::RUBYOBJ) {
|
|
373
|
+
NM_OBJECT_STORAGE_WB_UNPROTECT(left);
|
|
374
|
+
}
|
|
375
|
+
YaleStorage<DType> y(storage, left);
|
|
373
376
|
y.insert(slice, right);
|
|
374
377
|
}
|
|
375
378
|
|
|
@@ -1456,9 +1459,25 @@ void nm_yale_storage_mark(STORAGE* storage_base) {
|
|
|
1456
1459
|
YALE_STORAGE* storage = (YALE_STORAGE*)storage_base;
|
|
1457
1460
|
|
|
1458
1461
|
if (storage && storage->dtype == nm::RUBYOBJ) {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
+
/*
|
|
1463
|
+
* Yale slice references are lightweight wrappers around another
|
|
1464
|
+
* YALE_STORAGE. They may have no A array or capacity of their own, but
|
|
1465
|
+
* Ruby objects returned through the reference still live in the source
|
|
1466
|
+
* storage. Mark the source's initialized A entries so GC cannot reclaim
|
|
1467
|
+
* those objects while the reference is alive.
|
|
1468
|
+
*/
|
|
1469
|
+
YALE_STORAGE* src = reinterpret_cast<YALE_STORAGE*>(storage->src);
|
|
1470
|
+
|
|
1471
|
+
size_t size = src ? nm::yale_storage::get_size(src) : 0;
|
|
1472
|
+
if (src && src->a && size > 0) {
|
|
1473
|
+
VALUE* a = reinterpret_cast<VALUE*>(src->a);
|
|
1474
|
+
/*
|
|
1475
|
+
* Yale object storage keeps real Ruby VALUEs in the native A array.
|
|
1476
|
+
* Marking them exactly is important after mutation, because otherwise
|
|
1477
|
+
* GC may reclaim recently assigned objects before later iteration.
|
|
1478
|
+
*/
|
|
1479
|
+
for (size_t i = 0; i < size; ++i) rb_gc_mark(a[i]);
|
|
1480
|
+
}
|
|
1462
1481
|
}
|
|
1463
1482
|
}
|
|
1464
1483
|
|
data/lib/nmatrix/mkmf.rb
CHANGED
data/lib/nmatrix/nmatrix.rb
CHANGED
|
@@ -727,7 +727,7 @@ class NMatrix
|
|
|
727
727
|
end
|
|
728
728
|
|
|
729
729
|
# Do the actual construction.
|
|
730
|
-
n = NMatrix.new(new_shape, opts)
|
|
730
|
+
n = NMatrix.new(new_shape, **opts)
|
|
731
731
|
|
|
732
732
|
# Figure out where to start concatenation. We don't know where it will end,
|
|
733
733
|
# because each matrix may have own size along concat dimension.
|
data/lib/nmatrix/version.rb
CHANGED
data/spec/io_spec.rb
CHANGED
|
@@ -51,6 +51,7 @@ describe NMatrix::IO do
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
it "reads MATLAB .mat file containing a single square sparse matrix" do
|
|
54
|
+
skip "Mat5Reader was removed from pnmatrix" unless defined?(NMatrix::IO::Matlab::Mat5Reader)
|
|
54
55
|
pending("not yet implemented for NMatrix-JRuby") if jruby?
|
|
55
56
|
n = NMatrix::IO::Matlab.load_mat("spec/4x4_sparse.mat")
|
|
56
57
|
expect(n[0,0]).to eq(2)
|
|
@@ -62,19 +63,20 @@ describe NMatrix::IO do
|
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
it "reads MATLAB .mat file containing a single dense integer matrix" do
|
|
66
|
+
skip "Mat5Reader was removed from pnmatrix" unless defined?(NMatrix::IO::Matlab::Mat5Reader)
|
|
65
67
|
n = NMatrix::IO::Matlab.load_mat("spec/4x5_dense.mat")
|
|
66
68
|
m = NMatrix.new([4,5], [16,17,18,19,20,15,14,13,12,11,6,7,8,9,10,5,4,3,2,1])
|
|
67
69
|
expect(n).to eq(m)
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
it "reads MATLAB .mat file containing a single dense double matrix" do
|
|
73
|
+
skip "Mat5Reader was removed from pnmatrix" unless defined?(NMatrix::IO::Matlab::Mat5Reader)
|
|
71
74
|
n = NMatrix::IO::Matlab.load_mat("spec/2x2_dense_double.mat")
|
|
72
75
|
m = NMatrix.new(2, [1.1, 2.0, 3.0, 4.0], dtype: :float64)
|
|
73
76
|
expect(n).to eq(m)
|
|
74
77
|
end
|
|
75
78
|
|
|
76
79
|
it "loads and saves MatrixMarket .mtx file containing a single large sparse double matrix" do
|
|
77
|
-
pending "spec disabled because it's so slow"
|
|
78
80
|
n = NMatrix::IO::Market.load("spec/utm5940.mtx")
|
|
79
81
|
NMatrix::IO::Market.save(n, "spec/utm5940.saved.mtx")
|
|
80
82
|
expect(`wc -l spec/utm5940.mtx`.split[0]).to eq(`wc -l spec/utm5940.saved.mtx`.split[0])
|
data/spec/lapack_core_spec.rb
CHANGED
|
@@ -273,8 +273,9 @@ describe "NMatrix::LAPACK functions with internal implementations" do
|
|
|
273
273
|
end
|
|
274
274
|
|
|
275
275
|
expect(s).to be_within(err).of(s_true)
|
|
276
|
-
|
|
277
|
-
expect(
|
|
276
|
+
# Singular vectors are only defined up to sign (or complex phase).
|
|
277
|
+
expect(u.abs).to be_within(err).of(u_true.abs)
|
|
278
|
+
expect(vt.abs).to be_within(err).of(vt_true.abs)
|
|
278
279
|
|
|
279
280
|
expect(s.dtype).to eq(a.abs_dtype)
|
|
280
281
|
expect(u.dtype).to eq(dtype)
|
|
@@ -306,8 +307,9 @@ describe "NMatrix::LAPACK functions with internal implementations" do
|
|
|
306
307
|
end
|
|
307
308
|
|
|
308
309
|
expect(s).to be_within(err).of(s_true)
|
|
309
|
-
|
|
310
|
-
expect(
|
|
310
|
+
# Singular vectors are only defined up to sign (or complex phase).
|
|
311
|
+
expect(u.abs).to be_within(err).of(u_true.abs)
|
|
312
|
+
expect(vt.abs).to be_within(err).of(vt_true.abs)
|
|
311
313
|
end
|
|
312
314
|
|
|
313
315
|
|
|
@@ -337,8 +339,9 @@ describe "NMatrix::LAPACK functions with internal implementations" do
|
|
|
337
339
|
end
|
|
338
340
|
|
|
339
341
|
expect(eigenvalues).to be_within(err).of(eigenvalues_true)
|
|
340
|
-
|
|
341
|
-
expect(
|
|
342
|
+
# Eigenvectors are only defined up to sign (or complex phase).
|
|
343
|
+
expect(vr.abs).to be_within(err).of(vr_true.abs)
|
|
344
|
+
expect(vl.abs).to be_within(err).of(vl_true.abs)
|
|
342
345
|
|
|
343
346
|
expect(eigenvalues.dtype).to eq(NMatrix.upcast(dtype, :complex64))
|
|
344
347
|
expect(vr.dtype).to eq(NMatrix.upcast(dtype, :complex64))
|
|
@@ -384,7 +387,8 @@ describe "NMatrix::LAPACK functions with internal implementations" do
|
|
|
384
387
|
end
|
|
385
388
|
|
|
386
389
|
expect(eigenvalues).to be_within(err).of(eigenvalues_true)
|
|
387
|
-
|
|
390
|
+
# Eigenvectors are only defined up to sign (or complex phase).
|
|
391
|
+
expect(vr.abs).to be_within(err).of(vr_true.abs)
|
|
388
392
|
expect(vl).to be_within(err).of(vl_true)
|
|
389
393
|
|
|
390
394
|
expect(eigenvalues.dtype).to eq(dtype)
|
|
@@ -441,7 +445,8 @@ describe "NMatrix::LAPACK functions with internal implementations" do
|
|
|
441
445
|
end
|
|
442
446
|
|
|
443
447
|
expect(eigenvalues).to be_within(err).of(eigenvalues_true)
|
|
444
|
-
|
|
448
|
+
# Eigenvectors are only defined up to sign (or complex phase).
|
|
449
|
+
expect(vr.abs).to be_within(err).of(vr_true.abs)
|
|
445
450
|
end
|
|
446
451
|
end
|
|
447
452
|
end
|
data/spec/math_spec.rb
CHANGED
|
@@ -82,8 +82,10 @@ describe "math" do
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
it "should correctly apply elementwise natural log" do
|
|
85
|
-
expect(@m.log).to
|
|
86
|
-
|
|
85
|
+
expect(@m.log).to be_within(1e-14).of(
|
|
86
|
+
N.new(@size, [0, Math.log(2), Math.log(3), Math.log(4)],
|
|
87
|
+
dtype: :float64, stype: stype)
|
|
88
|
+
)
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
it "should correctly apply elementwise log with arbitrary base" do
|
|
@@ -264,6 +266,7 @@ describe "math" do
|
|
|
264
266
|
expect(r).to eq(b)
|
|
265
267
|
rescue NotImplementedError
|
|
266
268
|
pending "potrf! not implemented without plugins"
|
|
269
|
+
raise
|
|
267
270
|
end
|
|
268
271
|
end
|
|
269
272
|
|
|
@@ -280,6 +283,7 @@ describe "math" do
|
|
|
280
283
|
expect(r).to eq(b)
|
|
281
284
|
rescue NotImplementedError
|
|
282
285
|
pending "potrf! not implemented without plugins"
|
|
286
|
+
raise
|
|
283
287
|
end
|
|
284
288
|
end
|
|
285
289
|
|
|
@@ -295,6 +299,7 @@ describe "math" do
|
|
|
295
299
|
expect(l).to eq(l_true)
|
|
296
300
|
rescue NotImplementedError
|
|
297
301
|
pending "potrf! not implemented without plugins"
|
|
302
|
+
raise
|
|
298
303
|
end
|
|
299
304
|
end
|
|
300
305
|
end
|
|
@@ -325,11 +330,13 @@ describe "math" do
|
|
|
325
330
|
begin
|
|
326
331
|
q,r = a.factorize_qr
|
|
327
332
|
|
|
328
|
-
|
|
329
|
-
expect(
|
|
333
|
+
# QR factors are only defined up to matched column/row signs.
|
|
334
|
+
expect(q.abs).to be_within(err).of(q_solution.abs)
|
|
335
|
+
expect(r.abs).to be_within(err).of(r_solution.abs)
|
|
330
336
|
|
|
331
337
|
rescue NotImplementedError
|
|
332
338
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
339
|
+
raise
|
|
333
340
|
end
|
|
334
341
|
end
|
|
335
342
|
|
|
@@ -359,11 +366,13 @@ describe "math" do
|
|
|
359
366
|
begin
|
|
360
367
|
q,r = a.factorize_qr
|
|
361
368
|
|
|
362
|
-
|
|
363
|
-
expect(
|
|
369
|
+
# QR factors are only defined up to matched column/row signs.
|
|
370
|
+
expect(q.abs).to be_within(err).of(q_solution.abs)
|
|
371
|
+
expect(r.abs).to be_within(err).of(r_solution.abs)
|
|
364
372
|
|
|
365
373
|
rescue NotImplementedError
|
|
366
374
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
375
|
+
raise
|
|
367
376
|
end
|
|
368
377
|
end
|
|
369
378
|
|
|
@@ -387,11 +396,13 @@ describe "math" do
|
|
|
387
396
|
begin
|
|
388
397
|
q,r = a.factorize_qr
|
|
389
398
|
|
|
390
|
-
|
|
391
|
-
expect(
|
|
399
|
+
# QR factors are only defined up to matched column/row signs.
|
|
400
|
+
expect(q.abs).to be_within(err).of(q_solution.abs)
|
|
401
|
+
expect(r.abs).to be_within(err).of(r_solution.abs)
|
|
392
402
|
|
|
393
403
|
rescue NotImplementedError
|
|
394
404
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
405
|
+
raise
|
|
395
406
|
end
|
|
396
407
|
end
|
|
397
408
|
|
|
@@ -416,6 +427,7 @@ describe "math" do
|
|
|
416
427
|
|
|
417
428
|
rescue NotImplementedError
|
|
418
429
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
430
|
+
raise
|
|
419
431
|
end
|
|
420
432
|
end
|
|
421
433
|
|
|
@@ -444,6 +456,7 @@ describe "math" do
|
|
|
444
456
|
|
|
445
457
|
rescue NotImplementedError
|
|
446
458
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
459
|
+
raise
|
|
447
460
|
end
|
|
448
461
|
end
|
|
449
462
|
end
|
|
@@ -461,7 +474,7 @@ describe "math" do
|
|
|
461
474
|
end
|
|
462
475
|
|
|
463
476
|
it "should correctly invert a matrix in place (bang)" do
|
|
464
|
-
pending("not yet implemented for :object dtype") if dtype == :object
|
|
477
|
+
pending("not yet implemented for :object dtype") if dtype == :object && defined?(NMatrix::LAPACKE)
|
|
465
478
|
a = NMatrix.new(:dense, 5, [1, 8,-9, 7, 5,
|
|
466
479
|
0, 1, 0, 4, 4,
|
|
467
480
|
0, 0, 1, 2, 5,
|
|
@@ -529,6 +542,7 @@ describe "math" do
|
|
|
529
542
|
expect(a.dot(a.pinv)).to be_within(err).of(b)
|
|
530
543
|
rescue NotImplementedError
|
|
531
544
|
pending "Suppressing a NotImplementedError when the atlas plugin is not available"
|
|
545
|
+
raise
|
|
532
546
|
end
|
|
533
547
|
|
|
534
548
|
else
|
|
@@ -539,6 +553,7 @@ describe "math" do
|
|
|
539
553
|
expect(a.dot(a.pinv)).to be_within(err).of(b)
|
|
540
554
|
rescue NotImplementedError
|
|
541
555
|
pending "Suppressing a NotImplementedError when the atlas plugin is not available"
|
|
556
|
+
raise
|
|
542
557
|
end
|
|
543
558
|
end
|
|
544
559
|
end
|
|
@@ -556,6 +571,7 @@ describe "math" do
|
|
|
556
571
|
expect(b.dot(a.dot(b))).to be_within(err).of(b)
|
|
557
572
|
rescue NotImplementedError
|
|
558
573
|
pending "Suppressing a NotImplementedError when the atlas plugin is not available"
|
|
574
|
+
raise
|
|
559
575
|
end
|
|
560
576
|
|
|
561
577
|
else
|
|
@@ -567,6 +583,7 @@ describe "math" do
|
|
|
567
583
|
expect(b.dot(a.dot(b))).to be_within(err).of(b)
|
|
568
584
|
rescue NotImplementedError
|
|
569
585
|
pending "Suppressing a NotImplementedError when the atlas plugin is not available"
|
|
586
|
+
raise
|
|
570
587
|
end
|
|
571
588
|
end
|
|
572
589
|
end
|
|
@@ -1134,7 +1151,6 @@ describe "math" do
|
|
|
1134
1151
|
expect(@c.det).to be_within(@err).of(-18)
|
|
1135
1152
|
end
|
|
1136
1153
|
it "computes the exact determinant of 2x2 matrix" do
|
|
1137
|
-
pending("not yet implemented for :object dtype") if dtype == :object
|
|
1138
1154
|
if dtype == :byte
|
|
1139
1155
|
expect{@a.det_exact}.to raise_error(DataTypeError)
|
|
1140
1156
|
else
|
|
@@ -1166,7 +1182,6 @@ describe "math" do
|
|
|
1166
1182
|
end
|
|
1167
1183
|
|
|
1168
1184
|
it "scales the matrix by a given factor and return the result" do
|
|
1169
|
-
pending("not yet implemented for :object dtype") if dtype == :object
|
|
1170
1185
|
if integer_dtype? dtype
|
|
1171
1186
|
expect{@m.scale 2.0}.to raise_error(DataTypeError)
|
|
1172
1187
|
else
|
|
@@ -1178,7 +1193,6 @@ describe "math" do
|
|
|
1178
1193
|
end
|
|
1179
1194
|
|
|
1180
1195
|
it "scales the matrix in place by a given factor" do
|
|
1181
|
-
pending("not yet implemented for :object dtype") if dtype == :object
|
|
1182
1196
|
if dtype == :int8
|
|
1183
1197
|
expect{@m.scale! 2}.to raise_error(DataTypeError)
|
|
1184
1198
|
else
|
|
@@ -1196,7 +1210,6 @@ describe "math" do
|
|
|
1196
1210
|
context "matrix_norm" do
|
|
1197
1211
|
ALL_DTYPES.each do |dtype|
|
|
1198
1212
|
context dtype do
|
|
1199
|
-
pending("not yet implemented for :object dtype") if dtype == :object
|
|
1200
1213
|
before do
|
|
1201
1214
|
@n = NMatrix.new([3,3], [-4,-3,-2,
|
|
1202
1215
|
-1, 0, 1,
|
|
@@ -1215,6 +1228,7 @@ describe "math" do
|
|
|
1215
1228
|
|
|
1216
1229
|
rescue NotImplementedError
|
|
1217
1230
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
1231
|
+
raise
|
|
1218
1232
|
end
|
|
1219
1233
|
end
|
|
1220
1234
|
end
|
|
@@ -1240,6 +1254,7 @@ describe "math" do
|
|
|
1240
1254
|
expect(@n.matrix_norm(-2)).to be_within(@matrix_norm_TOLERANCE).of(0.0)
|
|
1241
1255
|
rescue NotImplementedError
|
|
1242
1256
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
1257
|
+
raise
|
|
1243
1258
|
end
|
|
1244
1259
|
expect(@n.matrix_norm(-1)).to eq(6)
|
|
1245
1260
|
end
|
|
@@ -1293,6 +1308,7 @@ describe "math" do
|
|
|
1293
1308
|
|
|
1294
1309
|
rescue NotImplementedError
|
|
1295
1310
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
1311
|
+
raise
|
|
1296
1312
|
end
|
|
1297
1313
|
end
|
|
1298
1314
|
|
|
@@ -1308,6 +1324,7 @@ describe "math" do
|
|
|
1308
1324
|
|
|
1309
1325
|
rescue NotImplementedError
|
|
1310
1326
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
1327
|
+
raise
|
|
1311
1328
|
end
|
|
1312
1329
|
end
|
|
1313
1330
|
|
|
@@ -1323,6 +1340,7 @@ describe "math" do
|
|
|
1323
1340
|
|
|
1324
1341
|
rescue NotImplementedError
|
|
1325
1342
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
1343
|
+
raise
|
|
1326
1344
|
end
|
|
1327
1345
|
end
|
|
1328
1346
|
|
|
@@ -1338,6 +1356,7 @@ describe "math" do
|
|
|
1338
1356
|
|
|
1339
1357
|
rescue NotImplementedError
|
|
1340
1358
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
1359
|
+
raise
|
|
1341
1360
|
end
|
|
1342
1361
|
end
|
|
1343
1362
|
|
|
@@ -1353,6 +1372,7 @@ describe "math" do
|
|
|
1353
1372
|
|
|
1354
1373
|
rescue NotImplementedError
|
|
1355
1374
|
pending "Suppressing a NotImplementedError when the lapacke plugin is not available"
|
|
1375
|
+
raise
|
|
1356
1376
|
end
|
|
1357
1377
|
end
|
|
1358
1378
|
|
data/spec/stress_test_spec.rb
CHANGED
|
@@ -1,6 +1,94 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe NMatrix do
|
|
4
|
+
def force_gc
|
|
5
|
+
20.times do
|
|
6
|
+
Array.new(1_000) { |i| "gc-pressure-#{i}" }
|
|
7
|
+
GC.start
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def build_dense_object_reference
|
|
12
|
+
values = Array.new(64 * 64) { |i| "dense-object-#{i}" }
|
|
13
|
+
matrix = NMatrix.new([64, 64], values, stype: :dense, dtype: :object)
|
|
14
|
+
|
|
15
|
+
matrix[50...64, 45...64]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def build_yale_object_reference
|
|
19
|
+
rng = Random.new(5678)
|
|
20
|
+
matrix = NMatrix.new([35, 35], nil, stype: :yale, dtype: :object, capacity: 1)
|
|
21
|
+
|
|
22
|
+
700.times do |i|
|
|
23
|
+
matrix[rng.rand(35), rng.rand(35)] = "yale-object-#{i}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
matrix[3...30, 4...32]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_nested_yale_object_reference
|
|
30
|
+
rng = Random.new(6789)
|
|
31
|
+
matrix = NMatrix.new([45, 45], nil, stype: :yale, dtype: :object, capacity: 1)
|
|
32
|
+
|
|
33
|
+
1_000.times do |i|
|
|
34
|
+
matrix[rng.rand(45), rng.rand(45)] = "nested-yale-object-#{i}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
matrix[5...40, 6...39][7...28, 8...30]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_list_object_reference
|
|
41
|
+
matrix = NMatrix.new([30, 30], nil, stype: :list, dtype: :object)
|
|
42
|
+
|
|
43
|
+
30.times do |i|
|
|
44
|
+
matrix[i, (i * 7) % 30] = "list-object-#{i}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
matrix[4...28, 2...29]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def mutate_dense_object_matrix_without_external_references
|
|
51
|
+
rng = Random.new(555)
|
|
52
|
+
matrix = NMatrix.new([80, 80], nil, stype: :dense, dtype: :object)
|
|
53
|
+
|
|
54
|
+
5_000.times do |i|
|
|
55
|
+
matrix[rng.rand(80), rng.rand(80)] = "mut-dense-#{i}"
|
|
56
|
+
GC.start if (i % 25).zero?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
matrix
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def mutate_yale_object_matrix_without_external_references
|
|
63
|
+
rng = Random.new(333)
|
|
64
|
+
matrix = NMatrix.new([80, 80], nil, stype: :yale, dtype: :object, capacity: 1)
|
|
65
|
+
|
|
66
|
+
5_000.times do |i|
|
|
67
|
+
matrix[rng.rand(80), rng.rand(80)] = "mut-yale-#{i}"
|
|
68
|
+
GC.start if (i % 25).zero?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
matrix
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def mutate_list_object_matrix_without_external_references
|
|
75
|
+
rng = Random.new(888)
|
|
76
|
+
matrix = NMatrix.new([30, 30], nil, stype: :list, dtype: :object)
|
|
77
|
+
|
|
78
|
+
500.times do |i|
|
|
79
|
+
matrix[rng.rand(30), rng.rand(30)] = "mut-list-#{i}"
|
|
80
|
+
GC.start if (i % 10).zero?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
matrix
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def touch_reference(reference)
|
|
87
|
+
reference.each_stored_with_indices do |value, _i, _j|
|
|
88
|
+
value.to_s.hash
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
4
92
|
specify do
|
|
5
93
|
200.times do |i|
|
|
6
94
|
size = rand(500..1000)
|
|
@@ -15,4 +103,79 @@ describe NMatrix do
|
|
|
15
103
|
(m1*m2).det
|
|
16
104
|
end
|
|
17
105
|
end
|
|
106
|
+
|
|
107
|
+
specify "does not segfault when iterating a GC-stressed object Yale reference" do
|
|
108
|
+
reference = build_yale_object_reference
|
|
109
|
+
|
|
110
|
+
force_gc
|
|
111
|
+
|
|
112
|
+
touch_reference(reference)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
specify "does not segfault when iterating a GC-stressed nested object Yale reference" do
|
|
116
|
+
reference = build_nested_yale_object_reference
|
|
117
|
+
|
|
118
|
+
force_gc
|
|
119
|
+
|
|
120
|
+
touch_reference(reference)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
specify "does not segfault when iterating a GC-stressed object dense reference" do
|
|
124
|
+
reference = build_dense_object_reference
|
|
125
|
+
|
|
126
|
+
force_gc
|
|
127
|
+
|
|
128
|
+
reference.each_with_indices do |value, _i, _j|
|
|
129
|
+
value.to_s.hash
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
specify "does not segfault when iterating a GC-stressed object list reference" do
|
|
134
|
+
reference = build_list_object_reference
|
|
135
|
+
|
|
136
|
+
force_gc
|
|
137
|
+
|
|
138
|
+
touch_reference(reference)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
specify "does not segfault after GC-stressed object dense mutation" do
|
|
142
|
+
matrix = mutate_dense_object_matrix_without_external_references
|
|
143
|
+
|
|
144
|
+
force_gc
|
|
145
|
+
|
|
146
|
+
matrix.each do |value|
|
|
147
|
+
value.to_s.hash
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
specify "does not segfault after GC-stressed object Yale mutation" do
|
|
152
|
+
matrix = mutate_yale_object_matrix_without_external_references
|
|
153
|
+
|
|
154
|
+
force_gc
|
|
155
|
+
|
|
156
|
+
touch_reference(matrix)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
specify "does not segfault after GC-stressed object list mutation" do
|
|
160
|
+
matrix = mutate_list_object_matrix_without_external_references
|
|
161
|
+
|
|
162
|
+
force_gc
|
|
163
|
+
|
|
164
|
+
touch_reference(matrix)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
specify "does not expose partially built Yale object storage during a cast" do
|
|
168
|
+
previous_gc_stress = GC.stress
|
|
169
|
+
GC.stress = true
|
|
170
|
+
|
|
171
|
+
current_stock = NMatrix.new([30, 1], 0, stype: :yale, dtype: :float64)
|
|
172
|
+
current_stock[3] = -4
|
|
173
|
+
current_stock[7] = 10
|
|
174
|
+
current_stock[11] = 50
|
|
175
|
+
|
|
176
|
+
quantity_made = (current_stock < 0).cast(dtype: :int32) * current_stock * -1
|
|
177
|
+
expect(quantity_made[3]).to eq(4)
|
|
178
|
+
ensure
|
|
179
|
+
GC.stress = previous_gc_stress
|
|
180
|
+
end
|
|
18
181
|
end
|