carray 3.0.4 → 3.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.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +438 -0
  3. data/README.md +1 -1
  4. data/carray.gemspec +3 -2
  5. data/ext/ca_array_pool.c +25 -1
  6. data/ext/ca_axis_dispatch.c +19 -20
  7. data/ext/ca_axis_group.c +1 -1
  8. data/ext/ca_categorical_iterator.c +34 -27
  9. data/ext/ca_compare.c +4 -4
  10. data/ext/ca_composite_dispatch.c +9 -9
  11. data/ext/ca_kernel_iterator.c +122 -237
  12. data/ext/ca_kernel_iterator.h +522 -337
  13. data/ext/ca_obj_array.c +69 -19
  14. data/ext/ca_obj_bincmp.c +10 -1
  15. data/ext/ca_obj_binop.c +11 -1
  16. data/ext/ca_obj_bitarray.c +5 -1
  17. data/ext/ca_obj_bitfield.c +60 -43
  18. data/ext/ca_obj_byte_swap.c +4 -1
  19. data/ext/ca_obj_const_string.c +15 -3
  20. data/ext/ca_obj_face.c +44 -1
  21. data/ext/ca_obj_fake.c +3 -1
  22. data/ext/ca_obj_field.c +13 -3
  23. data/ext/ca_obj_grid.c +219 -17
  24. data/ext/ca_obj_meld.c +69 -113
  25. data/ext/ca_obj_moncmp.c +9 -1
  26. data/ext/ca_obj_monop.c +86 -12
  27. data/ext/ca_obj_object.c +118 -32
  28. data/ext/ca_obj_record.c +3 -0
  29. data/ext/ca_obj_reduce.c +19 -1
  30. data/ext/ca_obj_refer.c +80 -26
  31. data/ext/ca_obj_remap.c +69 -10
  32. data/ext/ca_obj_roll.c +27 -6
  33. data/ext/ca_obj_select.c +38 -6
  34. data/ext/ca_obj_select_axis.c +1 -0
  35. data/ext/ca_obj_shift.c +32 -7
  36. data/ext/ca_obj_stack.c +311 -111
  37. data/ext/ca_obj_stride.c +159 -64
  38. data/ext/ca_obj_string.c +27 -2
  39. data/ext/ca_obj_tile.c +28 -7
  40. data/ext/ca_obj_transpose.c +8 -4
  41. data/ext/ca_obj_triop.c +12 -1
  42. data/ext/ca_obj_window.c +80 -50
  43. data/ext/ca_op_ipower.c +12 -12
  44. data/ext/ca_sort_kernels.h +6 -3
  45. data/ext/caframe_csv_reader.c +1 -1
  46. data/ext/carray.h +35 -1
  47. data/ext/carray_access.c +43 -67
  48. data/ext/carray_address_basis.c +134 -0
  49. data/ext/carray_attribute.c +32 -0
  50. data/ext/carray_broadcast.c +3 -4
  51. data/ext/carray_build_flags.h +1 -1
  52. data/ext/carray_cast.c +251 -93
  53. data/ext/carray_cast_func.rb +56 -26
  54. data/ext/carray_conversion.c +11 -7
  55. data/ext/carray_core.c +814 -72
  56. data/ext/carray_count.c +6 -0
  57. data/ext/carray_element.c +22 -13
  58. data/ext/carray_factorize.c +20 -20
  59. data/ext/carray_generate.c +8 -2
  60. data/ext/carray_histogram.c +26 -4
  61. data/ext/carray_index_classifier.c +4 -4
  62. data/ext/carray_internal.h +159 -12
  63. data/ext/carray_kernels_bincmp.c +2174 -170
  64. data/ext/carray_kernels_binop.c +25653 -5487
  65. data/ext/carray_kernels_init.c +8 -8
  66. data/ext/carray_kernels_moncmp.c +96 -96
  67. data/ext/carray_kernels_monop.c +12253 -5235
  68. data/ext/carray_kernels_reduce_aggregate.c +6150 -2423
  69. data/ext/carray_kernels_reduce_boolean.c +1533 -15
  70. data/ext/carray_kernels_reduce_cumulative.c +2442 -882
  71. data/ext/carray_kernels_reduce_extreme.c +5658 -1661
  72. data/ext/carray_kernels_reduce_variance.c +2534 -402
  73. data/ext/carray_kernels_scan.c +368 -368
  74. data/ext/carray_kernels_search.c +4551 -604
  75. data/ext/carray_kernels_sort.c +659 -751
  76. data/ext/carray_kernels_triop.c +114 -114
  77. data/ext/carray_key_windows.c +4 -0
  78. data/ext/carray_lazy.c +47 -0
  79. data/ext/carray_loop.c +1 -1
  80. data/ext/carray_mask.c +175 -161
  81. data/ext/carray_median_percentile.c +4 -4
  82. data/ext/carray_memory_view.c +15 -9
  83. data/ext/carray_operator.c +152 -49
  84. data/ext/carray_order.c +230 -10
  85. data/ext/carray_random.c +22 -19
  86. data/ext/carray_scatter.c +24 -7
  87. data/ext/carray_slab.c +128 -29
  88. data/ext/carray_sort.c +224 -16
  89. data/ext/carray_sort_kernel.c +5 -7
  90. data/ext/carray_struct.c +35 -43
  91. data/ext/carray_test.c +74 -19
  92. data/ext/carray_time_text.c +6 -4
  93. data/ext/carray_utils.c +63 -21
  94. data/ext/extconf.rb +11 -0
  95. data/ext/mkkernel.rb +546 -199
  96. data/ext/ruby_carray.c +4 -1
  97. data/ext/version.h +4 -4
  98. data/lib/carray/attribute.rb +42 -5
  99. data/lib/carray/autoload_carray.rb +3 -3
  100. data/lib/carray/autoload_method_extension.rb +5 -3
  101. data/lib/carray/axis_group.rb +13 -7
  102. data/lib/carray/basics.rb +28 -8
  103. data/lib/carray/block_iterator.rb +17 -3
  104. data/lib/carray/categorical.rb +35 -23
  105. data/lib/carray/categorical_iterator.rb +8 -8
  106. data/lib/carray/complex.rb +2 -2
  107. data/lib/carray/conditional.rb +3 -2
  108. data/lib/carray/const_string.rb +10 -0
  109. data/lib/carray/data_type_extension.rb +8 -1
  110. data/lib/carray/fixlen_string.rb +41 -2
  111. data/lib/carray/frame/column_verbs.rb +48 -16
  112. data/lib/carray/frame/concat.rb +17 -3
  113. data/lib/carray/frame/convert.rb +19 -0
  114. data/lib/carray/frame/csv_parser.rb +32 -32
  115. data/lib/carray/frame/describe.rb +4 -2
  116. data/lib/carray/frame/frame.rb +405 -85
  117. data/lib/carray/frame/group.rb +777 -64
  118. data/lib/carray/frame/io.rb +212 -72
  119. data/lib/carray/frame/join.rb +146 -37
  120. data/lib/carray/frame/records.rb +3 -3
  121. data/lib/carray/frame/reshape.rb +89 -11
  122. data/lib/carray/frame/sort.rb +57 -9
  123. data/lib/carray/frame/verbs.rb +384 -49
  124. data/lib/carray/frame/windows.rb +44 -6
  125. data/lib/carray/fuse_source.rb +31 -2
  126. data/lib/carray/inspect.rb +7 -2
  127. data/lib/carray/iterator.rb +32 -0
  128. data/lib/carray/lazy.rb +52 -8
  129. data/lib/carray/mask_gap_fill.rb +37 -17
  130. data/lib/carray/meld_reduce.rb +1 -1
  131. data/lib/carray/methods/choose.rb +3 -2
  132. data/lib/carray/methods/is_in.rb +8 -1
  133. data/lib/carray/methods/meshgrid.rb +3 -1
  134. data/lib/carray/runtime.rb +47 -8
  135. data/lib/carray/segment_iterator.rb +35 -16
  136. data/lib/carray/serialize.rb +53 -19
  137. data/lib/carray/stack.rb +33 -18
  138. data/lib/carray/string.rb +8 -0
  139. data/lib/carray/string_operation_extension.rb +13 -5
  140. data/lib/carray/struct_builder.rb +5 -3
  141. data/lib/carray/time.rb +112 -68
  142. data/lib/carray/window_iterator.rb +16 -4
  143. data/yard-stubs/ca_obj_array.rb +26 -1
  144. data/yard-stubs/ca_obj_transpose.rb +5 -4
  145. data/yard-stubs/carray_attribute.rb +3 -1
  146. data/yard-stubs/carray_cast.rb +10 -5
  147. data/yard-stubs/carray_conversion.rb +13 -3
  148. data/yard-stubs/carray_element.rb +7 -6
  149. data/yard-stubs/carray_mask.rb +28 -4
  150. data/yard-stubs/carray_math.rb +45 -20
  151. data/yard-stubs/carray_memory_view.rb +7 -0
  152. data/yard-stubs/carray_order.rb +100 -4
  153. data/yard-stubs/carray_scatter.rb +7 -5
  154. data/yard-stubs/carray_test.rb +7 -4
  155. metadata +18 -4
data/ext/carray_slab.c CHANGED
@@ -13,7 +13,8 @@
13
13
 
14
14
  Internals:
15
15
  - ca_slab_iter_state_t (see carray_slab.h) is the per-call state struct.
16
- - Each entry point stack-allocates a zeroed state, populates it via
16
+ - Each entry point heap-allocates a zeroed state under a window guard
17
+ (slab_state_new), populates it via
17
18
  slab_state_init (= nofail; only validation + scalar field fills),
18
19
  then rb_ensure-runs slab_state_run_body (= acquires T1 substrate +
19
20
  scratch + output, runs per-form loop) and slab_state_finish (= frees
@@ -21,9 +22,8 @@
21
22
  - All heap acquisition lives in run_body so init-time rb_raise paths
22
23
  cannot leak; rb_ensure guarantees finish on both normal and
23
24
  exception exit.
24
- - Conservative stack scan keeps the state struct's VALUE fields alive
25
- across rb_yield; no dmark callback is required because the state is
26
- not Ruby-wrapped.
25
+ - The guard marks the state's VALUE fields, and runs slab_state_finish
26
+ from a finalizer if the block's Fiber is abandoned mid-walk.
27
27
 
28
28
  --------------------------------------------------------------------------- */
29
29
 
@@ -122,9 +122,9 @@ slab_state_init (ca_slab_iter_state_t *st, VALUE source, int8_t form,
122
122
  {
123
123
  CArray *src;
124
124
 
125
- /* Caller stack-allocates with `{0}` which sets VALUE fields to Qfalse
126
- (= 0). Body code uses `== Qnil` to distinguish unset slots from
127
- populated CArray handles, so we must promote them to Qnil here. */
125
+ /* The state arrives zeroed, which leaves VALUE fields Qfalse (= 0).
126
+ Body code uses `== Qnil` to distinguish unset slots from populated
127
+ CArray handles, so we must promote them to Qnil here. */
128
128
  st->self = Qnil;
129
129
  st->slab_view = Qnil;
130
130
  st->carrier = Qnil;
@@ -346,6 +346,26 @@ ca_slab_build_output_view (int8_t data_type, ca_size_t bytes,
346
346
  return TypedData_Wrap_Struct(rb_cCAWrap, &cawrap_data_type, cw);
347
347
  }
348
348
 
349
+ /* The array the slab views move onto when the walk ends (see
350
+ slab_state_release_views), made with the carrier and held by it. It is
351
+ made here rather than at the end: the end runs in an ensure, after the
352
+ block, and an allocation failing there would leave the walk open and
353
+ the views pointing into freed scratch. */
354
+ static void
355
+ ca_slab_prepare_backing (ca_slab_iter_state_t *st)
356
+ {
357
+ CArray *cw, *backing;
358
+ volatile VALUE bv;
359
+ TypedData_Get_Struct(st->carrier, CArray, &carray_data_type, cw);
360
+ bv = rb_carray_new(cw->data_type, cw->ndim, cw->dim, cw->bytes, NULL);
361
+ TypedData_Get_Struct(bv, CArray, &carray_data_type, backing);
362
+ if ( cw->mask ) {
363
+ ca_create_mask(backing);
364
+ }
365
+ rb_ivar_set(st->carrier, rb_intern("__slab_backing__"), bv);
366
+ RB_GC_GUARD(bv);
367
+ }
368
+
349
369
  /* Common setup of the input slab view for all 4 forms. Decides ALIAS
350
370
  vs SCRATCH from T1's slab_strides[0] vs src->bytes equality, builds
351
371
  the slab_view + (when SCRATCH) carrier in place. The mask travels
@@ -465,6 +485,10 @@ ca_slab_setup_input_slab_view (ca_slab_iter_state_t *st, CArray *src,
465
485
  }
466
486
  }
467
487
 
488
+ if ( st->carrier != Qnil ) {
489
+ ca_slab_prepare_backing(st);
490
+ }
491
+
468
492
  /* The slab is a window onto the source when the fiber is contiguous and
469
493
  a copy in the iterator's scratch when it is not, so writing through it
470
494
  landed on one axis and vanished on the next. Nothing in the surface
@@ -502,8 +526,12 @@ ca_slab_advance_slab_view (ca_slab_iter_state_t *st,
502
526
  if ( slab_cs->mask != NULL ) {
503
527
  CAStride *mask_cs = (CAStride *) slab_cs->mask;
504
528
  if ( m_in != NULL ) {
529
+ /* m_in points into the walk's copy of the source's mask, not into
530
+ the source's mask itself, so the offset is taken from the data
531
+ cell: it is what the slab reads once the copy is gone. */
505
532
  mask_cs->ptr = (char *) m_in;
506
- mask_cs->base_offset = (ca_size_t) ((char *) m_in - src->mask->ptr);
533
+ mask_cs->base_offset = (ca_size_t) ((p_in - src->ptr) / src->bytes)
534
+ * src->mask->bytes;
507
535
  }
508
536
  else {
509
537
  /* The walk was opened on a source without a mask and the block
@@ -861,7 +889,6 @@ ca_slab_reduce_setup (ca_slab_iter_state_t *st, CArray **out_src,
861
889
  ca_size_t *out_n)
862
890
  {
863
891
  CArray *src;
864
- CArray *out;
865
892
  ca_size_t fiber_len;
866
893
  ca_size_t out_dim[CA_RANK_MAX];
867
894
  int8_t out_ndim;
@@ -904,7 +931,6 @@ ca_slab_reduce_setup (ca_slab_iter_state_t *st, CArray **out_src,
904
931
 
905
932
  st->output = rb_carray_new(st->out_data_type, out_ndim, out_dim,
906
933
  st->out_bytes, NULL);
907
- TypedData_Get_Struct(st->output, CArray, &carray_data_type, out);
908
934
 
909
935
  /* FIBER_CONTIG is naxes==1 only (T1 substrate contract). Multi-axis
910
936
  omits the flag and uses bare CA_SLAB_AXES; T1 picks an in-src
@@ -1178,8 +1204,11 @@ slab_state_run_body (VALUE arg)
1178
1204
  as it has a ptr. Before the scratch they point into is freed, the
1179
1205
  input slab and its carrier are moved onto a copy of the last slab,
1180
1206
  held by the carrier, and the internal output view lets go of its
1181
- buffer. A slab that is a window onto the source itself points into
1182
- the source and needs nothing. */
1207
+ buffer. The copy is made into the array ca_slab_prepare_backing set
1208
+ aside, so nothing here allocates. A slab that is a window onto the
1209
+ source keeps reading the source's data, but its mask points into the
1210
+ walk's copy of the mask, which finish frees: it lets go of that copy
1211
+ and reads the source's mask from then on. */
1183
1212
  static void
1184
1213
  slab_state_release_views (ca_slab_iter_state_t *st)
1185
1214
  {
@@ -1188,6 +1217,13 @@ slab_state_release_views (ca_slab_iter_state_t *st)
1188
1217
  TypedData_Get_Struct(st->output_slab_view, CArray, &carray_data_type, ov);
1189
1218
  ov->ptr = NULL;
1190
1219
  }
1220
+ if ( st->carrier == Qnil && st->slab_view != Qnil ) {
1221
+ CArray *cs;
1222
+ TypedData_Get_Struct(st->slab_view, CArray, &carray_data_type, cs);
1223
+ if ( cs->mask ) {
1224
+ cs->mask->ptr = NULL;
1225
+ }
1226
+ }
1191
1227
  if ( st->carrier != Qnil && st->slab_view != Qnil ) {
1192
1228
  CArray *cw, *cs, *backing;
1193
1229
  volatile VALUE bv;
@@ -1196,12 +1232,12 @@ slab_state_release_views (ca_slab_iter_state_t *st)
1196
1232
  if ( cw->ptr == NULL ) {
1197
1233
  return;
1198
1234
  }
1199
- backing = ca_copy(cw);
1200
- bv = ca_wrap_struct(backing);
1201
- if ( cw->mask && ! backing->mask ) {
1202
- ca_create_mask(backing);
1235
+ bv = rb_ivar_get(st->carrier, rb_intern("__slab_backing__"));
1236
+ TypedData_Get_Struct(bv, CArray, &carray_data_type, backing);
1237
+ memcpy(backing->ptr, cw->ptr, (size_t) (cw->elements * cw->bytes));
1238
+ if ( cw->mask && cw->mask->ptr ) {
1239
+ memcpy(backing->mask->ptr, cw->mask->ptr, (size_t) cw->elements);
1203
1240
  }
1204
- rb_ivar_set(st->carrier, rb_intern("__slab_backing__"), bv);
1205
1241
  if ( cs->ptr ) {
1206
1242
  cs->ptr = backing->ptr;
1207
1243
  }
@@ -1252,6 +1288,72 @@ slab_state_finish (VALUE arg)
1252
1288
  /* Entry points: CArray#map_slab / #reduce_slab / #each_slab */
1253
1289
  /* ------------------------------------------------------------------- */
1254
1290
 
1291
+ /* The walk yields to the block with its windows open. The state lives on
1292
+ the heap under a window guard, so that if the block's Fiber is abandoned
1293
+ (an Enumerator stopped with #next and dropped) the guard's finalizer
1294
+ still runs slab_state_finish and the source is let go. */
1295
+
1296
+ static void
1297
+ slab_state_mark (void *p)
1298
+ {
1299
+ ca_slab_iter_state_t *st = (ca_slab_iter_state_t *) p;
1300
+ rb_gc_mark(st->self);
1301
+ rb_gc_mark(st->slab_view);
1302
+ rb_gc_mark(st->carrier);
1303
+ rb_gc_mark(st->mask_carrier);
1304
+ rb_gc_mark(st->output);
1305
+ rb_gc_mark(st->output_slab_view);
1306
+ rb_gc_mark(st->init_val);
1307
+ }
1308
+
1309
+ static void
1310
+ slab_state_finish_abandoned (void *p)
1311
+ {
1312
+ slab_state_finish((VALUE)(uintptr_t) p);
1313
+ }
1314
+
1315
+ typedef struct {
1316
+ ca_slab_iter_state_t *st;
1317
+ VALUE guard;
1318
+ VALUE sentinel;
1319
+ } slab_run_t;
1320
+
1321
+ static VALUE
1322
+ slab_run_body (VALUE arg)
1323
+ {
1324
+ return slab_state_run_body((VALUE)(uintptr_t) ((slab_run_t *) arg)->st);
1325
+ }
1326
+
1327
+ static VALUE
1328
+ slab_run_close (VALUE arg)
1329
+ {
1330
+ slab_run_t *r = (slab_run_t *) arg;
1331
+ ca_window_guard_disarm(r->guard, r->sentinel);
1332
+ return slab_state_finish((VALUE)(uintptr_t) r->st);
1333
+ }
1334
+
1335
+ /* Allocates the state; slab_state_init fills it, then slab_run walks. */
1336
+ static ca_slab_iter_state_t *
1337
+ slab_state_new (slab_run_t *r)
1338
+ {
1339
+ ca_slab_iter_state_t *st = ZALLOC(ca_slab_iter_state_t);
1340
+ st->self = st->slab_view = st->carrier = st->mask_carrier = Qnil;
1341
+ st->output = st->output_slab_view = st->init_val = Qnil;
1342
+ r->st = st;
1343
+ r->guard = ca_window_guard_arm(slab_state_finish_abandoned, slab_state_mark,
1344
+ st, &r->sentinel);
1345
+ return st;
1346
+ }
1347
+
1348
+ static VALUE
1349
+ slab_run (slab_run_t *r)
1350
+ {
1351
+ VALUE result = rb_ensure(slab_run_body, (VALUE) r, slab_run_close, (VALUE) r);
1352
+ RB_GC_GUARD(r->guard);
1353
+ RB_GC_GUARD(r->sentinel);
1354
+ return result;
1355
+ }
1356
+
1255
1357
  /* Shared kwarg parse: returns axis (required) + data_type / init via
1256
1358
  out-params. init_out is set to Qundef if the `:init` key was absent
1257
1359
  (= caller distinguishes per-slab vs per-element reduce forms). */
@@ -1299,7 +1401,7 @@ slab_extract_kwargs (int argc, VALUE *argv, const char *method_name)
1299
1401
  static VALUE
1300
1402
  rb_ca_map_slab (int argc, VALUE *argv, VALUE self)
1301
1403
  {
1302
- ca_slab_iter_state_t st = {0};
1404
+ slab_run_t r;
1303
1405
  VALUE kw, axis, data_type, init_unused;
1304
1406
  VALUE result;
1305
1407
 
@@ -1312,10 +1414,9 @@ rb_ca_map_slab (int argc, VALUE *argv, VALUE self)
1312
1414
  rb_raise(rb_eLocalJumpError, "CArray#map_slab: block required");
1313
1415
  }
1314
1416
 
1315
- slab_state_init(&st, self, CA_SLAB_FORM_MAP, axis, data_type, Qundef);
1417
+ slab_state_init(slab_state_new(&r), self, CA_SLAB_FORM_MAP, axis, data_type, Qundef);
1316
1418
 
1317
- result = rb_ensure(slab_state_run_body, (VALUE)(uintptr_t)&st,
1318
- slab_state_finish, (VALUE)(uintptr_t)&st);
1419
+ result = slab_run(&r);
1319
1420
  RB_GC_GUARD(self);
1320
1421
  return result;
1321
1422
  }
@@ -1323,7 +1424,7 @@ rb_ca_map_slab (int argc, VALUE *argv, VALUE self)
1323
1424
  static VALUE
1324
1425
  rb_ca_reduce_slab (int argc, VALUE *argv, VALUE self)
1325
1426
  {
1326
- ca_slab_iter_state_t st = {0};
1427
+ slab_run_t r;
1327
1428
  VALUE kw, axis, data_type, init_val;
1328
1429
  VALUE result;
1329
1430
  int8_t form;
@@ -1341,10 +1442,9 @@ rb_ca_reduce_slab (int argc, VALUE *argv, VALUE self)
1341
1442
  form = (init_val == Qundef) ? CA_SLAB_FORM_REDUCE_SLAB
1342
1443
  : CA_SLAB_FORM_REDUCE_FIBER;
1343
1444
 
1344
- slab_state_init(&st, self, form, axis, data_type, init_val);
1445
+ slab_state_init(slab_state_new(&r), self, form, axis, data_type, init_val);
1345
1446
 
1346
- result = rb_ensure(slab_state_run_body, (VALUE)(uintptr_t)&st,
1347
- slab_state_finish, (VALUE)(uintptr_t)&st);
1447
+ result = slab_run(&r);
1348
1448
  RB_GC_GUARD(self);
1349
1449
  return result;
1350
1450
  }
@@ -1352,7 +1452,7 @@ rb_ca_reduce_slab (int argc, VALUE *argv, VALUE self)
1352
1452
  static VALUE
1353
1453
  rb_ca_each_slab (int argc, VALUE *argv, VALUE self)
1354
1454
  {
1355
- ca_slab_iter_state_t st = {0};
1455
+ slab_run_t r;
1356
1456
  VALUE kw, axis, data_type_unused, init_unused;
1357
1457
  VALUE result;
1358
1458
 
@@ -1374,10 +1474,9 @@ rb_ca_each_slab (int argc, VALUE *argv, VALUE self)
1374
1474
  2, args, RB_PASS_KEYWORDS);
1375
1475
  }
1376
1476
 
1377
- slab_state_init(&st, self, CA_SLAB_FORM_EACH, axis, Qnil, Qundef);
1477
+ slab_state_init(slab_state_new(&r), self, CA_SLAB_FORM_EACH, axis, Qnil, Qundef);
1378
1478
 
1379
- result = rb_ensure(slab_state_run_body, (VALUE)(uintptr_t)&st,
1380
- slab_state_finish, (VALUE)(uintptr_t)&st);
1479
+ result = slab_run(&r);
1381
1480
  RB_GC_GUARD(self);
1382
1481
  return result;
1383
1482
  }
data/ext/carray_sort.c CHANGED
@@ -103,16 +103,207 @@ sort_addr_merge (struct sort_addr_key *a, struct sort_addr_key *aux,
103
103
  ca_size_t n, int (*cmp)(const void *, const void *))
104
104
  CA_SORT_MERGE_BY_CMP(struct sort_addr_key)
105
105
 
106
+ /* -------------------------------------------------------------------
107
+
108
+ Multi-key sort by refinement (MSD), for keys of the numeric data types.
109
+
110
+ The first key is sorted whole by the typed pair kernel
111
+ (ca_sort_quick_pair_*); then only the runs of rows that tie on it are
112
+ sorted by the second key, and so on, until no run is left or the keys
113
+ run out. The answer is the one sort_addr_cmp gives:
114
+
115
+ - a pair sorts by (value, row), so rows that tie on every key stay in
116
+ row order (the stable tie-break of sort_addr_cmp);
117
+ - each run is split into numbers, NaN and masked cells, in the order
118
+ sort_addr_cmp puts them (numbers, NaN, masked for masked_last;
119
+ masked, numbers, NaN otherwise). The NaN and the masked cells of a
120
+ run tie among themselves (CA_FLOAT_ORDER_CMP / sort_addr_cmp), so
121
+ each is a run for the next key. The split is a stable one done in
122
+ the load, not ca_partition_nan_pair_*, which allocates on every call
123
+ and is called here once per run;
124
+ - -0.0 and 0.0 tie, by == as by CA_FLOAT_ORDER_CMP.
125
+
126
+ A run is (lo, n): the rows perm[lo..lo+n). Within a run the rows are
127
+ in row order, since they tie on every key before it, so the stable
128
+ split keeps them so.
129
+
130
+ ------------------------------------------------------------------- */
131
+
132
+ typedef struct {
133
+ ca_size_t lo;
134
+ ca_size_t n;
135
+ } msd_run_t;
136
+
137
+ typedef struct {
138
+ msd_run_t *runs;
139
+ ca_size_t count;
140
+ } msd_runs_t;
141
+
142
+ static void
143
+ msd_add_run (msd_runs_t *out, ca_size_t lo, ca_size_t n)
144
+ {
145
+ if ( out && n > 1 ) {
146
+ out->runs[out->count].lo = lo;
147
+ out->runs[out->count].n = n;
148
+ out->count++;
149
+ }
150
+ }
151
+
152
+ /* Sort each run of +in+ by +key+, writing the new order into perm, and
153
+ add the runs that still tie to +out+ (NULL for the last key). */
154
+ #define DEFINE_MSD_REFINE(TYPE, SUFFIX, IS_FLOAT) \
155
+ static void \
156
+ msd_refine_##SUFFIX (CArray *key, ca_size_t *perm, void *vbuf, \
157
+ msd_runs_t *in, msd_runs_t *out, int masked_last) \
158
+ { \
159
+ const CA_U(TYPE) *ptr = (const CA_U(TYPE) *) key->ptr; \
160
+ const boolean8_t *m = ( key->mask ) ? (boolean8_t *) key->mask->ptr : NULL; \
161
+ ca_pair_##SUFFIX *buf = (ca_pair_##SUFFIX *) vbuf; \
162
+ ca_size_t r; \
163
+ \
164
+ for (r=0; r<in->count; r++) { \
165
+ ca_size_t lo = in->runs[r].lo; \
166
+ ca_size_t n = in->runs[r].n; \
167
+ ca_size_t *p = perm + lo; \
168
+ ca_size_t n_num = 0, n_nan = 0, n_mask = 0; \
169
+ ca_size_t o_num, o_nan, o_mask; \
170
+ ca_size_t k, s; \
171
+ \
172
+ for (k=0; k<n; k++) { \
173
+ ca_size_t row = p[k]; \
174
+ if ( m && m[row] ) n_mask++; \
175
+ else if ( IS_FLOAT && isnan((double) ptr[row]) ) n_nan++; \
176
+ else n_num++; \
177
+ } \
178
+ o_mask = masked_last ? n_num + n_nan : 0; \
179
+ o_num = masked_last ? 0 : n_mask; \
180
+ o_nan = o_num + n_num; \
181
+ { \
182
+ ca_size_t i_num = o_num, i_nan = o_nan, i_mask = o_mask; \
183
+ for (k=0; k<n; k++) { \
184
+ ca_size_t row = p[k]; \
185
+ TYPE v = ptr[row]; \
186
+ ca_size_t at; \
187
+ if ( m && m[row] ) at = i_mask++; \
188
+ else if ( IS_FLOAT && isnan((double) v) ) at = i_nan++; \
189
+ else at = i_num++; \
190
+ buf[at].v = v; \
191
+ buf[at].i = row; \
192
+ } \
193
+ } \
194
+ ca_sort_quick_pair_##SUFFIX(buf + o_num, n_num); \
195
+ for (k=0; k<n; k++) { \
196
+ p[k] = buf[k].i; \
197
+ } \
198
+ if ( ! out ) continue; \
199
+ for (s=0, k=1; k<=n_num; k++) { \
200
+ if ( k == n_num || buf[o_num + k].v != buf[o_num + s].v ) { \
201
+ msd_add_run(out, lo + o_num + s, k - s); \
202
+ s = k; \
203
+ } \
204
+ } \
205
+ msd_add_run(out, lo + o_nan, n_nan); \
206
+ msd_add_run(out, lo + o_mask, n_mask); \
207
+ } \
208
+ }
209
+
210
+ DEFINE_MSD_REFINE(int8_t, i8, 0)
211
+ DEFINE_MSD_REFINE(uint8_t, u8, 0)
212
+ DEFINE_MSD_REFINE(int16_t, i16, 0)
213
+ DEFINE_MSD_REFINE(uint16_t, u16, 0)
214
+ DEFINE_MSD_REFINE(int32_t, i32, 0)
215
+ DEFINE_MSD_REFINE(uint32_t, u32, 0)
216
+ DEFINE_MSD_REFINE(int64_t, i64, 0)
217
+ DEFINE_MSD_REFINE(uint64_t, u64, 0)
218
+ DEFINE_MSD_REFINE(float32_t, f32, 1)
219
+ DEFINE_MSD_REFINE(double, f64, 1)
220
+
221
+ /* Whether every key has a typed pair kernel, so that MSD can sort them. */
222
+ static int
223
+ msd_is_sortable (struct cmp_base *base)
224
+ {
225
+ int j;
226
+ for (j=0; j<base->n; j++) {
227
+ switch ( base->ca[j]->data_type ) {
228
+ case CA_BOOLEAN: case CA_INT8: case CA_UINT8:
229
+ case CA_INT16: case CA_UINT16:
230
+ case CA_INT32: case CA_UINT32:
231
+ case CA_INT64: case CA_UINT64:
232
+ case CA_FLOAT32: case CA_FLOAT64:
233
+ break;
234
+ default:
235
+ return 0;
236
+ }
237
+ }
238
+ return 1;
239
+ }
240
+
241
+ typedef struct {
242
+ struct cmp_base *base;
243
+ ca_size_t *perm; /* elements rows, the answer */
244
+ void *buf; /* elements pairs of 16 bytes */
245
+ msd_run_t *runs[2]; /* elements / 2 + 1 runs each */
246
+ ca_size_t elements;
247
+ } msd_sort_t;
248
+
249
+ static void
250
+ msd_sort (msd_sort_t *s)
251
+ {
252
+ struct cmp_base *base = s->base;
253
+ msd_runs_t a, b, *in = &a, *out = &b;
254
+ ca_size_t i;
255
+ int j;
256
+
257
+ for (i=0; i<s->elements; i++) {
258
+ s->perm[i] = i;
259
+ }
260
+ a.runs = s->runs[0]; a.count = 0;
261
+ b.runs = s->runs[1]; b.count = 0;
262
+ msd_add_run(in, 0, s->elements);
263
+
264
+ for (j=0; j<base->n && in->count > 0; j++) {
265
+ CArray *key = base->ca[j];
266
+ msd_runs_t *next = ( j < base->n - 1 ) ? out : NULL;
267
+ msd_runs_t *t;
268
+ if ( next ) next->count = 0;
269
+ switch ( key->data_type ) {
270
+ case CA_BOOLEAN:
271
+ case CA_INT8: msd_refine_i8 (key, s->perm, s->buf, in, next, base->masked_last); break;
272
+ case CA_UINT8: msd_refine_u8 (key, s->perm, s->buf, in, next, base->masked_last); break;
273
+ case CA_INT16: msd_refine_i16(key, s->perm, s->buf, in, next, base->masked_last); break;
274
+ case CA_UINT16: msd_refine_u16(key, s->perm, s->buf, in, next, base->masked_last); break;
275
+ case CA_INT32: msd_refine_i32(key, s->perm, s->buf, in, next, base->masked_last); break;
276
+ case CA_UINT32: msd_refine_u32(key, s->perm, s->buf, in, next, base->masked_last); break;
277
+ case CA_INT64: msd_refine_i64(key, s->perm, s->buf, in, next, base->masked_last); break;
278
+ case CA_UINT64: msd_refine_u64(key, s->perm, s->buf, in, next, base->masked_last); break;
279
+ case CA_FLOAT32: msd_refine_f32(key, s->perm, s->buf, in, next, base->masked_last); break;
280
+ case CA_FLOAT64: msd_refine_f64(key, s->perm, s->buf, in, next, base->masked_last); break;
281
+ default:
282
+ rb_raise(rb_eRuntimeError, "%s: [BUG] data type %d has no pair kernel",
283
+ ca_calling_method_name(), (int) key->data_type);
284
+ }
285
+ if ( ! next ) break;
286
+ t = in; in = out; out = t;
287
+ }
288
+ }
289
+
290
+ /* ----------------------------------------------------------------- */
291
+
106
292
  typedef struct {
107
293
  struct sort_addr_key *data;
108
294
  struct sort_addr_key *aux; /* NULL: no object key, libc sorts */
109
295
  ca_size_t elements;
296
+ msd_sort_t *msd; /* non-NULL: every key is numeric, sort by MSD */
110
297
  } sort_addr_run_t;
111
298
 
112
299
  static VALUE
113
300
  sort_addr_run (VALUE arg)
114
301
  {
115
302
  sort_addr_run_t *r = (sort_addr_run_t *) arg;
303
+ if ( r->msd ) {
304
+ msd_sort(r->msd);
305
+ return Qnil;
306
+ }
116
307
  if ( r->aux ) {
117
308
  sort_addr_merge(r->data, r->aux, r->elements,
118
309
  (int (*)(const void*,const void*)) sort_addr_cmp);
@@ -145,8 +336,9 @@ rb_ca_s_sort_addr_kw (int argc, VALUE *argv, VALUE ropt, VALUE self)
145
336
  CArray *co;
146
337
  struct cmp_base base_s, *base = &base_s;
147
338
  struct sort_addr_key *data;
148
- volatile VALUE hlist = 0, hdata = 0, haux = 0, hmodes = 0;
339
+ volatile VALUE hlist = 0, hdata = 0, haux = 0, hmodes = 0, hruns0 = 0, hruns1 = 0;
149
340
  sort_addr_run_t run;
341
+ msd_sort_t msd;
150
342
  char *modes;
151
343
  int has_object = 0;
152
344
  ca_size_t elements;
@@ -203,26 +395,42 @@ rb_ca_s_sort_addr_kw (int argc, VALUE *argv, VALUE ropt, VALUE self)
203
395
  }
204
396
  modes[argc] = '\0';
205
397
 
206
- data = ALLOCV_N(struct sort_addr_key, hdata, elements);
207
- for (i=0; i<elements; i++) {
208
- data[i].i = i;
209
- data[i].base = base;
210
- }
211
-
212
- run.data = data;
213
- run.aux = has_object ? ALLOCV_N(struct sort_addr_key, haux, elements) : NULL;
214
- run.elements = elements;
215
- ca_attach_window(argc, base->ca, modes, sort_addr_run, (VALUE) &run);
216
-
217
398
  out = rb_ca_template_with_type(argv[0], INT2NUM(CA_SIZE), INT2NUM(0));
218
399
  TypedData_Get_Struct(out, CArray, &carray_data_type, co);
219
400
  q = (ca_size_t *) co->ptr;
220
-
221
- for (i=0; i<elements; i++) {
222
- *q = data[i].i;
223
- q++;
401
+
402
+ run.data = NULL;
403
+ run.aux = NULL;
404
+ run.elements = elements;
405
+ run.msd = NULL;
406
+
407
+ if ( msd_is_sortable(base) ) {
408
+ ca_size_t nruns = elements / 2 + 1;
409
+ msd.base = base;
410
+ msd.perm = q;
411
+ msd.buf = ALLOCV(hdata, (size_t) elements * sizeof(ca_pair_f64));
412
+ msd.runs[0] = ALLOCV_N(msd_run_t, hruns0, nruns);
413
+ msd.runs[1] = ( argc > 1 ) ? ALLOCV_N(msd_run_t, hruns1, nruns) : NULL;
414
+ msd.elements = elements;
415
+ run.msd = &msd;
416
+ ca_attach_window(argc, base->ca, modes, sort_addr_run, (VALUE) &run);
417
+ }
418
+ else {
419
+ data = ALLOCV_N(struct sort_addr_key, hdata, elements);
420
+ for (i=0; i<elements; i++) {
421
+ data[i].i = i;
422
+ data[i].base = base;
423
+ }
424
+ run.data = data;
425
+ run.aux = has_object ? ALLOCV_N(struct sort_addr_key, haux, elements) : NULL;
426
+ ca_attach_window(argc, base->ca, modes, sort_addr_run, (VALUE) &run);
427
+ for (i=0; i<elements; i++) {
428
+ q[i] = data[i].i;
429
+ }
224
430
  }
225
431
 
432
+ ALLOCV_END(hruns1);
433
+ ALLOCV_END(hruns0);
226
434
  ALLOCV_END(haux);
227
435
  ALLOCV_END(hdata);
228
436
  ALLOCV_END(hmodes);
@@ -453,16 +453,15 @@ DEFINE_PARTITION_NAN(double, f64);
453
453
  preserved), so argsort on NaN-containing input keeps NaN indices
454
454
  in ascending order.
455
455
 
456
- Implementation: 2-pass stable filter via xmalloc scratch (single
457
- xmalloc + xfree per fiber; negligible overhead vs the sort
458
- itself). */
456
+ Implementation: 2-pass stable filter through scratch, n pairs that
457
+ the caller provides. The caller takes it before it opens its walks:
458
+ an allocation here, inside the walk, would raise past their finish. */
459
459
  #define DEFINE_PARTITION_NAN_PAIR(TYPE, SUFFIX) \
460
460
  ca_size_t \
461
- ca_partition_nan_pair_##SUFFIX (ca_pair_##SUFFIX *a, ca_size_t n) \
461
+ ca_partition_nan_pair_##SUFFIX (ca_pair_##SUFFIX *a, ca_size_t n, \
462
+ ca_pair_##SUFFIX *scratch) \
462
463
  { \
463
464
  if ( n == 0 ) return 0; \
464
- ca_pair_##SUFFIX *scratch = \
465
- (ca_pair_##SUFFIX *) xmalloc((size_t) n * sizeof(ca_pair_##SUFFIX)); \
466
465
  ca_size_t fin = 0, nan_pos = 0; \
467
466
  for ( ca_size_t k = 0; k < n; k++ ) { \
468
467
  if ( !isnan(a[k].v) ) scratch[fin++] = a[k]; \
@@ -472,7 +471,6 @@ ca_partition_nan_pair_##SUFFIX (ca_pair_##SUFFIX *a, ca_size_t n) \
472
471
  if ( isnan(a[k].v) ) scratch[finite_count + nan_pos++] = a[k]; \
473
472
  } \
474
473
  memcpy(a, scratch, (size_t) n * sizeof(ca_pair_##SUFFIX)); \
475
- xfree(scratch); \
476
474
  return finite_count; \
477
475
  } \
478
476
  struct ca_partition_nan_pair_##SUFFIX##_eat_semicolon