cumo 0.14.0 → 0.14.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/CHANGELOG.md +14 -1
- data/Rakefile +29 -0
- data/ext/cumo/include/cumo/cuda/runtime.h +3 -2
- data/ext/cumo/include/cumo/ndloop.h +7 -0
- data/ext/cumo/narray/gen/tmpl/binary.c +50 -0
- data/ext/cumo/narray/gen/tmpl/binary_kernel.cu +24 -0
- data/ext/cumo/narray/gen/tmpl/map_with_index.c +18 -0
- data/ext/cumo/narray/gen/tmpl/unary.c +8 -0
- data/ext/cumo/narray/narray.c +1 -0
- data/ext/cumo/narray/ndloop.c +137 -16
- data/lib/cumo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0872662f2f59cb4486df50f2f9b6dee5fc62fe938f6c47bafdb1ebdfc744c2f6'
|
|
4
|
+
data.tar.gz: 248f4ea1224ee0ff46069d16b424b1c90a98d81b33610506c58d45ffb9693c08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: caf8d608d673afd9e71d3c421d2c096db503bb0054bf1ca846f33de738ccb2662ab53a6c715e6003f9a27b48d148d1e8f60f509076d53eee1b1bebf56c023e97
|
|
7
|
+
data.tar.gz: 78c2fd433b79741516f842b069adfa90c0452694a106554e99fd0ac5415684d6b82beb7f44dae63a09a745fc075bf21c1fb71276fa8ad691965f9ddd335e8d9a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
# 0.14.1 (2026/10/22)
|
|
2
|
+
|
|
3
|
+
Fixes:
|
|
4
|
+
|
|
5
|
+
* Fix a host loop such as `each` yielding the old values of an array after its block wrote the array through `store_binary`, an in-place `cumsum` or `cumprod`, or an in-place `map` or `map_with_index`, also one paused by an Enumerator (PR #631, PR #630)
|
|
6
|
+
* Fix an in-place `map_with_index` over a view that reaches an element more than once, such as `x[[0, 0, 0]]`, reading the element as it was before its own write. It now reads its own write, as `map` and numo do (PR #631)
|
|
7
|
+
* Fix the release gem recording the tag of the previous release as `Cumo::REVISION`, as the 0.14.0 gem does with `"v0.13.0-11-gafd8ead9"`. `rake release` now makes the tag before it builds the gem, so this gem gives `"v0.14.1"` (PR #626)
|
|
8
|
+
|
|
9
|
+
Changes:
|
|
10
|
+
|
|
11
|
+
* `+`, `-`, `*` and `/` between a complex array and a real array of the same precision, in either order, read the real array as it is rather than casting it to a complex array first. On 4M elements this makes SComplex with SFloat about 1.7 to 1.8 times faster, and DComplex divided by DFloat about 1.4 times. The answers are the same but for the sign of a NaN (PR #632)
|
|
12
|
+
* A host loop such as `each` whose block writes to the device copies the rest of the row, or of the array, again at once when copying the elements one at a time would cost more. With one write early in an `each_with_index` over 100,000 elements, the loop is about 90 times faster and about as fast as with no write. With a write at every 100th element it is about 12 times faster, and with a write at every other element about 16% slower (PR #631)
|
|
13
|
+
|
|
1
14
|
# 0.14.0 (2026/10/18)
|
|
2
15
|
|
|
3
16
|
Breaking changes:
|
|
4
17
|
|
|
5
|
-
* `cumo.h` no longer defines `CUMO_VERSION` or `CUMO_VERSION_CODE`. `Cumo::VERSION` and `Cumo::NArray::VERSION` are defined in Ruby
|
|
18
|
+
* `cumo.h` no longer defines `CUMO_VERSION` or `CUMO_VERSION_CODE`. `Cumo::VERSION` and `Cumo::NArray::VERSION` are defined in Ruby. The new `Cumo::REVISION` tells a release gem from one built from a checkout. A release gives its tag, and a gem built at a later commit gives something like `"v0.14.0-3-g1a2b3c4d"`. The 0.14.0 gem was built before its tag was made, so it gives `"v0.13.0-11-gafd8ead9"`. `rake release` makes the tag before the gem from the next release on (PR #617, PR #626)
|
|
6
19
|
|
|
7
20
|
Fixes:
|
|
8
21
|
|
data/Rakefile
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
4
|
require "rake/testtask"
|
|
5
|
+
require "rubygems/package"
|
|
6
|
+
|
|
7
|
+
Rake::Task["release"].clear
|
|
8
|
+
desc "Tag the version, build the gem at the tag, and push the branch, the tag and the gem"
|
|
9
|
+
task :release, [:remote] do |_, args|
|
|
10
|
+
git = ->(*argv) { IO.popen(["git", *argv], err: File::NULL, &:read).strip }
|
|
11
|
+
version = File.read(File.join(__dir__, "lib/cumo/version.rb"))[/VERSION = "([^"]+)"/, 1]
|
|
12
|
+
tag = "v#{version}"
|
|
13
|
+
gem_file = "pkg/cumo-#{version}.gem"
|
|
14
|
+
remote = args[:remote] || "origin"
|
|
15
|
+
raise "There are files that need to be committed first." unless system("git", "diff", "--quiet") && system("git", "diff-index", "--quiet", "--cached", "HEAD")
|
|
16
|
+
|
|
17
|
+
tagged = git.call("rev-parse", "-q", "--verify", "#{tag}^{commit}")
|
|
18
|
+
raise "#{tag} is on #{tagged[0, 8]}, not on HEAD." unless tagged.empty? || tagged == git.call("rev-parse", "HEAD")
|
|
19
|
+
|
|
20
|
+
sh "git", "tag", "-m", "Version #{version}", tag if tagged.empty?
|
|
21
|
+
begin
|
|
22
|
+
mkdir_p "pkg"
|
|
23
|
+
sh "gem", "build", "cumo.gemspec", "--output", gem_file
|
|
24
|
+
revision = Gem::Package.new(gem_file).spec.metadata["cumo_revision"]
|
|
25
|
+
raise "#{gem_file} records #{revision.inspect} as cumo_revision, not #{tag.inspect}." unless revision == tag
|
|
26
|
+
rescue StandardError
|
|
27
|
+
sh "git", "tag", "-d", tag if tagged.empty?
|
|
28
|
+
raise
|
|
29
|
+
end
|
|
30
|
+
sh "git", "push", remote, "HEAD"
|
|
31
|
+
sh "git", "push", remote, "refs/tags/#{tag}"
|
|
32
|
+
sh "gem", "push", gem_file unless ENV["gem_push"] == "no"
|
|
33
|
+
end
|
|
5
34
|
|
|
6
35
|
Rake::TestTask.new(:test) do |t|
|
|
7
36
|
t.libs << "test"
|
|
@@ -33,10 +33,11 @@ extern size_t cumo_cuda_sync_epoch;
|
|
|
33
33
|
// and reads again once the count moves.
|
|
34
34
|
extern size_t cumo_cuda_launch_epoch;
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// Answers the count before the write.
|
|
37
|
+
static inline size_t
|
|
37
38
|
cumo_cuda_runtime_note_device_write(void)
|
|
38
39
|
{
|
|
39
|
-
cumo_atomic_size_fetch_add(&cumo_cuda_launch_epoch, 1);
|
|
40
|
+
return cumo_atomic_size_fetch_add(&cumo_cuda_launch_epoch, 1);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// A pinned host buffer for reading device memory back: a copy into pinned
|
|
@@ -104,6 +104,13 @@ typedef struct {
|
|
|
104
104
|
// it will read next again if device memory was written since it was staged.
|
|
105
105
|
void cumo_na_ndloop_refresh_next(cumo_na_loop_t *lp, const void *next, size_t bytes);
|
|
106
106
|
|
|
107
|
+
// For a function that yields to Ruby and writes an array that was there
|
|
108
|
+
// before it, which cumo_na_ndloop_writes_existing answers: counts the element
|
|
109
|
+
// it just wrote, so that a loop it yields to before it ends reads it. A staged
|
|
110
|
+
// copy of its own has to hold the element already.
|
|
111
|
+
int cumo_na_ndloop_writes_existing(cumo_na_loop_t *lp);
|
|
112
|
+
void cumo_na_ndloop_wrote(cumo_na_loop_t *lp);
|
|
113
|
+
|
|
107
114
|
// spec of user function
|
|
108
115
|
typedef struct {
|
|
109
116
|
cumo_na_iter_func_t func; // user function
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
<% unless type_name == 'robject' %>
|
|
12
12
|
void <%="cumo_#{c_iter}_kernel_launch"%>(cumo_na_iarray_t* a1, cumo_na_iarray_t* a2, cumo_na_iarray_t* a3, cumo_na_indexer_t* indexer, int* divzero);
|
|
13
|
+
//<% if is_complex && %w[add sub mul div].include?(name) %>
|
|
14
|
+
void <%="cumo_#{c_iter}_r_kernel_launch"%>(cumo_na_iarray_t* a1, cumo_na_iarray_t* a2, cumo_na_iarray_t* a3, cumo_na_indexer_t* indexer, int real_is_left);
|
|
15
|
+
//<% end %>
|
|
16
|
+
//<% complex_of = {'sfloat' => ['scomplex', 'cumo_cSComplex'], 'dfloat' => ['dcomplex', 'cumo_cDComplex']}[type_name] if %w[add sub mul div].include?(name) %>
|
|
17
|
+
//<% if complex_of %>
|
|
18
|
+
VALUE <%="cumo_#{complex_of[0]}_#{name}_real_left"%>(VALUE real, VALUE complex);
|
|
19
|
+
//<% end %>
|
|
13
20
|
//<% if %w[add sub mul div mod bit_and bit_or bit_xor left_shift right_shift copysign].include?(name) %>
|
|
14
21
|
void <%="cumo_#{c_iter}_s_kernel_launch"%>(cumo_na_iarray_t* a1, dtype sv, cumo_na_iarray_t* a3, cumo_na_indexer_t* indexer, int* divzero, int scalar_is_left);
|
|
15
22
|
//<% end %>
|
|
@@ -171,6 +178,40 @@ static void
|
|
|
171
178
|
//<% end %>
|
|
172
179
|
#undef check_intdivzero
|
|
173
180
|
|
|
181
|
+
//<% if is_complex && %w[add sub mul div].include?(name) %>
|
|
182
|
+
// The operands stay in the order they were written in, so that a shape
|
|
183
|
+
// mismatch names them as it always did.
|
|
184
|
+
static void
|
|
185
|
+
<%=c_iter%>_r(cumo_na_loop_t *const lp)
|
|
186
|
+
{
|
|
187
|
+
int real_is_left = *(int*)(lp->opt_ptr);
|
|
188
|
+
cumo_na_iarray_t a1 = cumo_na_make_iarray(&lp->args[real_is_left ? 1 : 0]);
|
|
189
|
+
cumo_na_iarray_t a2 = cumo_na_make_iarray(&lp->args[real_is_left ? 0 : 1]);
|
|
190
|
+
cumo_na_iarray_t a3 = cumo_na_make_iarray(&lp->args[2]);
|
|
191
|
+
cumo_na_indexer_t indexer = cumo_na_make_indexer(&lp->args[0]);
|
|
192
|
+
|
|
193
|
+
<%="cumo_#{c_iter}_r_kernel_launch"%>(&a1,&a2,&a3,&indexer,real_is_left);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// A real operand of this precision is read as it is rather than cast to a
|
|
197
|
+
// complex array first.
|
|
198
|
+
static VALUE
|
|
199
|
+
<%=c_func%>_real(VALUE lhs, VALUE rhs, int real_is_left)
|
|
200
|
+
{
|
|
201
|
+
cumo_ndfunc_arg_in_t ain[2] = {{real_is_left ? cRT : cT,0},{real_is_left ? cT : cRT,0}};
|
|
202
|
+
cumo_ndfunc_arg_out_t aout[1] = {{cT,0}};
|
|
203
|
+
cumo_ndfunc_t ndf = { <%=c_iter%>_r, CUMO_STRIDE_LOOP|CUMO_NDF_INDEXER_LOOP|CUMO_NDF_ANY_ORDER, 2, 1, ain, aout };
|
|
204
|
+
|
|
205
|
+
return cumo_na_ndloop3(&ndf, &real_is_left, 2, lhs, rhs);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
VALUE
|
|
209
|
+
<%="cumo_#{type_name}_#{name}_real_left"%>(VALUE real, VALUE self)
|
|
210
|
+
{
|
|
211
|
+
return <%=c_func%>_real(real, self, 1);
|
|
212
|
+
}
|
|
213
|
+
//<% end %>
|
|
214
|
+
|
|
174
215
|
static VALUE
|
|
175
216
|
<%=c_func%>_self(VALUE self, VALUE other)
|
|
176
217
|
{
|
|
@@ -202,6 +243,11 @@ static VALUE
|
|
|
202
243
|
return cumo_na_ndloop3(&ndf_s, &y, 1, self);
|
|
203
244
|
}
|
|
204
245
|
//<% end %>
|
|
246
|
+
//<% if is_complex && %w[add sub mul div].include?(name) %>
|
|
247
|
+
if (rb_obj_class(other) == cRT) {
|
|
248
|
+
return <%=c_func%>_real(self, other, 0);
|
|
249
|
+
}
|
|
250
|
+
//<% end %>
|
|
205
251
|
cumo_ndfunc_arg_in_t ain[2] = {{cT,0},{cT,0}};
|
|
206
252
|
cumo_ndfunc_arg_out_t aout[1] = {{cT,0}};
|
|
207
253
|
<% if type_name == 'robject' %>
|
|
@@ -230,6 +276,10 @@ static VALUE
|
|
|
230
276
|
klass = cumo_na_upcast(rb_obj_class(self),rb_obj_class(other));
|
|
231
277
|
if (klass==cT) {
|
|
232
278
|
return <%=c_func%>_self(self, other);
|
|
279
|
+
//<% if complex_of %>
|
|
280
|
+
} else if (rb_obj_class(other) == <%=complex_of[1]%> && rb_obj_class(self) == cT) {
|
|
281
|
+
return <%="cumo_#{complex_of[0]}_#{name}_real_left"%>(self, other);
|
|
282
|
+
//<% end %>
|
|
233
283
|
} else {
|
|
234
284
|
v = rb_funcall(klass, cumo_id_cast, 1, self);
|
|
235
285
|
return rb_funcall(v, <%=cumo_id_op%>, 1, other);
|
|
@@ -214,5 +214,29 @@ void <%="cumo_#{c_iter}_kernel_launch"%>(cumo_na_iarray_t* a1, cumo_na_iarray_t*
|
|
|
214
214
|
cumo_cuda_runtime_check_kernel_launch();
|
|
215
215
|
}
|
|
216
216
|
//<% end %>
|
|
217
|
+
|
|
218
|
+
//<% if is_complex && %w[add sub mul div].include?(name) %>
|
|
219
|
+
// A real operand of the same precision is made complex as it is read, which is
|
|
220
|
+
// what casting it first did, without the array the cast wrote.
|
|
221
|
+
<% indexer_dims(true).each do |idim| %>
|
|
222
|
+
__global__ void <%="cumo_#{c_iter}_r_kernel_dim#{idim}"%>(cumo_na_iarray_t a1, cumo_na_iarray_t a2, cumo_na_iarray_t a3, cumo_na_indexer_t indexer, int real_is_left)
|
|
223
|
+
{
|
|
224
|
+
for (uint64_t i = blockIdx.x * blockDim.x + threadIdx.x; i < indexer.total_size; i += blockDim.x * gridDim.x) {
|
|
225
|
+
cumo_na_indexer_set_dim<%=idim%>(&indexer, i);
|
|
226
|
+
dtype x = *(dtype*)(cumo_na_iarray_at_dim<%=idim%>(&a1, &indexer));
|
|
227
|
+
dtype y = m_from_real(*(rtype*)(cumo_na_iarray_at_dim<%=idim%>(&a2, &indexer)));
|
|
228
|
+
*(dtype*)(cumo_na_iarray_at_dim<%=idim%>(&a3, &indexer)) = real_is_left ? m_<%=name%>(y, x) : m_<%=name%>(x, y);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
<% end %>
|
|
232
|
+
|
|
233
|
+
void <%="cumo_#{c_iter}_r_kernel_launch"%>(cumo_na_iarray_t* a1, cumo_na_iarray_t* a2, cumo_na_iarray_t* a3, cumo_na_indexer_t* indexer, int real_is_left)
|
|
234
|
+
{
|
|
235
|
+
size_t grid_dim = cumo_get_grid_dim(indexer->total_size);
|
|
236
|
+
size_t block_dim = cumo_get_block_dim(indexer->total_size);
|
|
237
|
+
<%= indexer_switch("cumo_#{c_iter}_r_kernel", "*a1,*a2,*a3,*indexer,real_is_left", narrow: %w[a1 a2 a3]) %>
|
|
238
|
+
cumo_cuda_runtime_check_kernel_launch();
|
|
239
|
+
}
|
|
240
|
+
//<% end %>
|
|
217
241
|
#undef cumo_check_intdivzero
|
|
218
242
|
<% end %>
|
|
@@ -16,6 +16,12 @@ static void
|
|
|
16
16
|
size_t *c;
|
|
17
17
|
int nd, md;
|
|
18
18
|
|
|
19
|
+
// In place, a view that visits an element twice reads what it wrote the
|
|
20
|
+
// first time, so the copy the elements are read from takes the write too,
|
|
21
|
+
// after the element read next was copied again.
|
|
22
|
+
int own = lp->args[0].value == lp->args[1].value;
|
|
23
|
+
int counts = cumo_na_ndloop_writes_existing(lp);
|
|
24
|
+
|
|
19
25
|
c = (size_t*)(lp->opt_ptr);
|
|
20
26
|
cumo_na_with_index_dims(lp->ndim, &nd, &md);
|
|
21
27
|
a = ALLOCA_N(VALUE,md);
|
|
@@ -30,40 +36,52 @@ static void
|
|
|
30
36
|
if (idx1) {
|
|
31
37
|
if (idx2) {
|
|
32
38
|
for (; i--;) {
|
|
39
|
+
char *q = p1 + *idx1;
|
|
33
40
|
CUMO_GET_DATA_INDEX(p1,idx1,dtype,x);
|
|
34
41
|
x = yield_map_with_index(x,c,a,nd,md);
|
|
35
42
|
if (i) cumo_na_ndloop_refresh_next(lp, p1 + *idx1, sizeof(dtype));
|
|
36
43
|
if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }
|
|
44
|
+
if (own) *(dtype*)q = x;
|
|
37
45
|
CUMO_SET_DATA_INDEX(p2,idx2,dtype,x);
|
|
46
|
+
if (counts) cumo_na_ndloop_wrote(lp);
|
|
38
47
|
c[nd]++;
|
|
39
48
|
}
|
|
40
49
|
} else {
|
|
41
50
|
for (; i--;) {
|
|
51
|
+
char *q = p1 + *idx1;
|
|
42
52
|
CUMO_GET_DATA_INDEX(p1,idx1,dtype,x);
|
|
43
53
|
x = yield_map_with_index(x,c,a,nd,md);
|
|
44
54
|
if (i) cumo_na_ndloop_refresh_next(lp, p1 + *idx1, sizeof(dtype));
|
|
45
55
|
if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }
|
|
56
|
+
if (own) *(dtype*)q = x;
|
|
46
57
|
CUMO_SET_DATA_STRIDE(p2,s2,dtype,x);
|
|
58
|
+
if (counts) cumo_na_ndloop_wrote(lp);
|
|
47
59
|
c[nd]++;
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
} else {
|
|
51
63
|
if (idx2) {
|
|
52
64
|
for (; i--;) {
|
|
65
|
+
char *q = p1;
|
|
53
66
|
CUMO_GET_DATA_STRIDE(p1,s1,dtype,x);
|
|
54
67
|
x = yield_map_with_index(x,c,a,nd,md);
|
|
55
68
|
if (i) cumo_na_ndloop_refresh_next(lp, p1, sizeof(dtype));
|
|
56
69
|
if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }
|
|
70
|
+
if (own) *(dtype*)q = x;
|
|
57
71
|
CUMO_SET_DATA_INDEX(p2,idx2,dtype,x);
|
|
72
|
+
if (counts) cumo_na_ndloop_wrote(lp);
|
|
58
73
|
c[nd]++;
|
|
59
74
|
}
|
|
60
75
|
} else {
|
|
61
76
|
for (; i--;) {
|
|
77
|
+
char *q = p1;
|
|
62
78
|
CUMO_GET_DATA_STRIDE(p1,s1,dtype,x);
|
|
63
79
|
x = yield_map_with_index(x,c,a,nd,md);
|
|
64
80
|
if (i) cumo_na_ndloop_refresh_next(lp, p1, sizeof(dtype));
|
|
65
81
|
if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }
|
|
82
|
+
if (own) *(dtype*)q = x;
|
|
66
83
|
CUMO_SET_DATA_STRIDE(p2,s2,dtype,x);
|
|
84
|
+
if (counts) cumo_na_ndloop_wrote(lp);
|
|
67
85
|
c[nd]++;
|
|
68
86
|
}
|
|
69
87
|
}
|
|
@@ -19,6 +19,8 @@ static void
|
|
|
19
19
|
size_t i;
|
|
20
20
|
dtype x;
|
|
21
21
|
<% if name == 'map' %>
|
|
22
|
+
// RObject data is host memory, which no loop stages.
|
|
23
|
+
int counts = <%= type_name == 'robject' ? '0' : 'cumo_na_ndloop_writes_existing(lp)' %>;
|
|
22
24
|
if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }
|
|
23
25
|
<% else %>
|
|
24
26
|
CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>");
|
|
@@ -30,6 +32,7 @@ static void
|
|
|
30
32
|
x = m_<%=name%>(x);
|
|
31
33
|
<% if name == 'map' %>if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }<% end %>
|
|
32
34
|
CUMO_SET_DATA_INDEX(p2,idx2,dtype,x);
|
|
35
|
+
<% if name == 'map' %>if (counts) cumo_na_ndloop_wrote(lp);<% end %>
|
|
33
36
|
}
|
|
34
37
|
} else {
|
|
35
38
|
for (i=0; i<n; i++) {
|
|
@@ -37,6 +40,7 @@ static void
|
|
|
37
40
|
x = m_<%=name%>(x);
|
|
38
41
|
<% if name == 'map' %>if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }<% end %>
|
|
39
42
|
CUMO_SET_DATA_STRIDE(p2,s2,dtype,x);
|
|
43
|
+
<% if name == 'map' %>if (counts) cumo_na_ndloop_wrote(lp);<% end %>
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
} else {
|
|
@@ -46,6 +50,7 @@ static void
|
|
|
46
50
|
x = m_<%=name%>(x);
|
|
47
51
|
<% if name == 'map' %>if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }<% end %>
|
|
48
52
|
CUMO_SET_DATA_INDEX(p2,idx2,dtype,x);
|
|
53
|
+
<% if name == 'map' %>if (counts) cumo_na_ndloop_wrote(lp);<% end %>
|
|
49
54
|
}
|
|
50
55
|
} else {
|
|
51
56
|
//<% if need_align %>
|
|
@@ -58,6 +63,7 @@ static void
|
|
|
58
63
|
x = m_<%=name%>(((dtype*)p1)[i]);
|
|
59
64
|
if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }
|
|
60
65
|
((dtype*)p2)[i] = x;
|
|
66
|
+
if (counts) cumo_na_ndloop_wrote(lp);
|
|
61
67
|
<% else %>
|
|
62
68
|
((dtype*)p2)[i] = m_<%=name%>(((dtype*)p1)[i]);
|
|
63
69
|
<% end %>
|
|
@@ -72,6 +78,7 @@ static void
|
|
|
72
78
|
x = m_<%=name%>(*(dtype*)p1);
|
|
73
79
|
if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }
|
|
74
80
|
*(dtype*)p2 = x;
|
|
81
|
+
if (counts) cumo_na_ndloop_wrote(lp);
|
|
75
82
|
<% else %>
|
|
76
83
|
*(dtype*)p2 = m_<%=name%>(*(dtype*)p1);
|
|
77
84
|
<% end %>
|
|
@@ -87,6 +94,7 @@ static void
|
|
|
87
94
|
x = m_<%=name%>(x);
|
|
88
95
|
<% if name == 'map' %>if (cumo_cuda_runtime_sync_if_busy()) { CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>"); }<% end %>
|
|
89
96
|
CUMO_SET_DATA_STRIDE(p2,s2,dtype,x);
|
|
97
|
+
<% if name == 'map' %>if (counts) cumo_na_ndloop_wrote(lp);<% end %>
|
|
90
98
|
}
|
|
91
99
|
//<% end %>
|
|
92
100
|
}
|
data/ext/cumo/narray/narray.c
CHANGED
|
@@ -1997,6 +1997,7 @@ cumo_na_store_binary(int argc, VALUE *argv, VALUE self)
|
|
|
1997
1997
|
CUMO_SHOW_SYNCHRONIZE_WARNING_ONCE("cumo_na_store_binary", "any");
|
|
1998
1998
|
cumo_cuda_runtime_device_synchronize();
|
|
1999
1999
|
memcpy(ptr+cumo_na_get_offset(self), RSTRING_PTR(vstr)+offset, byte_size);
|
|
2000
|
+
cumo_cuda_runtime_note_device_write();
|
|
2000
2001
|
}
|
|
2001
2002
|
|
|
2002
2003
|
return SIZET2NUM(byte_size);
|
data/ext/cumo/narray/ndloop.c
CHANGED
|
@@ -79,8 +79,17 @@ typedef struct CUMO_NA_HOST_STAGE {
|
|
|
79
79
|
cumo_cuda_stage_t stage;
|
|
80
80
|
size_t epoch; // the launch count when the whole range was copied
|
|
81
81
|
size_t row_epoch; // the launch count when the current row was copied
|
|
82
|
+
size_t seen_epoch; // the launch count when the loop last looked
|
|
83
|
+
size_t row_cost; // what copying a row costs, see CUMO_NA_STAGE_COPY_COST
|
|
84
|
+
size_t range_cost; // what copying the whole range costs
|
|
85
|
+
size_t row_debt; // spent on single elements since the row was copied
|
|
86
|
+
size_t range_debt; // spent on rows and elements since the range was copied
|
|
82
87
|
} cumo_na_host_stage_t;
|
|
83
88
|
|
|
89
|
+
// A copy into pinned memory costs a wait before its bytes, about as long as
|
|
90
|
+
// moving this many bytes takes, so a copy of n bytes costs this plus n.
|
|
91
|
+
#define CUMO_NA_STAGE_COPY_COST (64u << 10)
|
|
92
|
+
|
|
84
93
|
#define LARG(lp,iarg) ((lp)->user.args[iarg])
|
|
85
94
|
#define LITER(lp,idim,iarg) ((lp)->xargs[iarg].iter[idim])
|
|
86
95
|
#define LITER_SRC(lp,idim) ((lp)->src_iter[idim])
|
|
@@ -476,6 +485,22 @@ ndloop_alloc(cumo_na_md_loop_t *lp, cumo_ndfunc_t *nf, VALUE args,
|
|
|
476
485
|
|
|
477
486
|
static void ndloop_unstage_host_reads(cumo_na_md_loop_t *lp);
|
|
478
487
|
|
|
488
|
+
// Whether the loop wrote an array that was there before it: an input it
|
|
489
|
+
// overwrites, or an output it took over from an input. A new output is
|
|
490
|
+
// nothing anyone has staged.
|
|
491
|
+
static int
|
|
492
|
+
ndloop_writes_existing(cumo_na_md_loop_t *lp)
|
|
493
|
+
{
|
|
494
|
+
int j;
|
|
495
|
+
|
|
496
|
+
for (j=0; j<lp->narg; j++) {
|
|
497
|
+
if (j < lp->nin ? (lp->xargs[j].flag & CUMO_NDL_WRITE) : lp->xargs[j].flag == CUMO_NDL_READ_WRITE) {
|
|
498
|
+
return 1;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return 0;
|
|
502
|
+
}
|
|
503
|
+
|
|
479
504
|
static VALUE
|
|
480
505
|
ndloop_release(VALUE vlp)
|
|
481
506
|
{
|
|
@@ -483,6 +508,13 @@ ndloop_release(VALUE vlp)
|
|
|
483
508
|
VALUE v;
|
|
484
509
|
cumo_na_md_loop_t *lp = (cumo_na_md_loop_t*)(vlp);
|
|
485
510
|
|
|
511
|
+
// A host loop, and a library call such as the scan of cumsum, writes
|
|
512
|
+
// device memory without a launch to count it, and a host loop reading a
|
|
513
|
+
// staged copy of that memory reads it again only once the count moves.
|
|
514
|
+
// Counted here so that a loop left by a raise counts too.
|
|
515
|
+
if (ndloop_writes_existing(lp)) {
|
|
516
|
+
cumo_cuda_runtime_note_device_write();
|
|
517
|
+
}
|
|
486
518
|
ndloop_unstage_host_reads(lp);
|
|
487
519
|
for (j=0; j < lp->narg; j++) {
|
|
488
520
|
v = LARG(lp,j).value;
|
|
@@ -821,6 +853,7 @@ ndloop_set_output_narray(cumo_ndfunc_t *nf, cumo_na_md_loop_t *lp, int k,
|
|
|
821
853
|
}
|
|
822
854
|
|
|
823
855
|
j = lp->nin + k;
|
|
856
|
+
lp->xargs[j].flag = flag;
|
|
824
857
|
LARG(lp,j).ndim = nd = nf->aout[k].dim;
|
|
825
858
|
ndloop_set_stepidx(lp, j, v, dim_map, flag);
|
|
826
859
|
if (nd > 0) {
|
|
@@ -1900,6 +1933,8 @@ static void
|
|
|
1900
1933
|
ndloop_stage_row(cumo_na_md_loop_t *lp)
|
|
1901
1934
|
{
|
|
1902
1935
|
cumo_na_host_stage_t *hs = lp->hs;
|
|
1936
|
+
// Read before the copy: a write counted while it runs leaves it behind.
|
|
1937
|
+
size_t now = cumo_cuda_launch_epoch_now();
|
|
1903
1938
|
int j;
|
|
1904
1939
|
|
|
1905
1940
|
for (j=0; j<hs->narg; j++) {
|
|
@@ -1910,7 +1945,38 @@ ndloop_stage_row(cumo_na_md_loop_t *lp)
|
|
|
1910
1945
|
ndloop_stage_bytes(lp, j, lo, hi, &min, &len);
|
|
1911
1946
|
ndloop_stage_dma(lp, j, min, len);
|
|
1912
1947
|
}
|
|
1913
|
-
hs->row_epoch =
|
|
1948
|
+
hs->row_epoch = now;
|
|
1949
|
+
hs->row_debt = 0;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// Copies the whole range of every staged input again.
|
|
1953
|
+
static void
|
|
1954
|
+
ndloop_stage_range(cumo_na_md_loop_t *lp)
|
|
1955
|
+
{
|
|
1956
|
+
cumo_na_host_stage_t *hs = lp->hs;
|
|
1957
|
+
size_t now = cumo_cuda_launch_epoch_now();
|
|
1958
|
+
int j;
|
|
1959
|
+
|
|
1960
|
+
for (j=0; j<hs->narg; j++) {
|
|
1961
|
+
if (hs->ptr[j] == NULL) continue;
|
|
1962
|
+
ndloop_stage_dma(lp, j, hs->min[j], hs->len[j]);
|
|
1963
|
+
}
|
|
1964
|
+
hs->epoch = hs->row_epoch = now;
|
|
1965
|
+
hs->row_debt = hs->range_debt = 0;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
// The bytes a row of argument j spans, wherever the row starts. Bit rows
|
|
1969
|
+
// cover one word more than their bits when they start mid-word.
|
|
1970
|
+
static size_t
|
|
1971
|
+
ndloop_stage_row_len(cumo_na_md_loop_t *lp, int j)
|
|
1972
|
+
{
|
|
1973
|
+
ssize_t lo, hi;
|
|
1974
|
+
|
|
1975
|
+
ndloop_stage_extent(lp, j, lp->ndim, lp->hs->ndim, 0, &lo, &hi);
|
|
1976
|
+
if (rb_obj_is_kind_of(LARG(lp,j).value, cumo_cBit)) {
|
|
1977
|
+
return ((size_t)(hi - lo) / CUMO_NB + 2) * sizeof(CUMO_BIT_DIGIT);
|
|
1978
|
+
}
|
|
1979
|
+
return (size_t)(hi - lo) + LARG(lp,j).elmsz;
|
|
1914
1980
|
}
|
|
1915
1981
|
|
|
1916
1982
|
// Reads the inputs of a host loop into pinned memory and points the
|
|
@@ -1974,16 +2040,9 @@ ndloop_stage_host_reads(cumo_na_md_loop_t *lp)
|
|
|
1974
2040
|
hs->rows_only = 1;
|
|
1975
2041
|
total = 0;
|
|
1976
2042
|
for (j=0; j<narg; j++) {
|
|
1977
|
-
ssize_t lo, hi;
|
|
1978
2043
|
size_t len;
|
|
1979
2044
|
if (hs->ptr[j] == NULL) continue;
|
|
1980
|
-
|
|
1981
|
-
if (rb_obj_is_kind_of(LARG(lp,j).value, cumo_cBit)) {
|
|
1982
|
-
// Which words a row covers depends on where it starts.
|
|
1983
|
-
len = ((size_t)(hi - lo) / CUMO_NB + 2) * sizeof(CUMO_BIT_DIGIT);
|
|
1984
|
-
} else {
|
|
1985
|
-
len = (size_t)(hi - lo) + LARG(lp,j).elmsz;
|
|
1986
|
-
}
|
|
2045
|
+
len = ndloop_stage_row_len(lp, j);
|
|
1987
2046
|
if (len > CUMO_CUDA_STAGE_CACHE_MAX) {
|
|
1988
2047
|
hs->ptr[j] = NULL;
|
|
1989
2048
|
hs->direct++;
|
|
@@ -1997,59 +2056,121 @@ ndloop_stage_host_reads(cumo_na_md_loop_t *lp)
|
|
|
1997
2056
|
if (hs->direct) cumo_cuda_runtime_device_synchronize();
|
|
1998
2057
|
if (rows == 0) return;
|
|
1999
2058
|
cumo_cuda_runtime_stage_alloc(&hs->stage, total);
|
|
2059
|
+
hs->row_cost = CUMO_NA_STAGE_COPY_COST + total;
|
|
2060
|
+
hs->seen_epoch = cumo_cuda_launch_epoch_now();
|
|
2000
2061
|
return;
|
|
2001
2062
|
}
|
|
2002
2063
|
if (hs->direct) cumo_cuda_runtime_device_synchronize();
|
|
2003
2064
|
if (total == 0) return;
|
|
2004
2065
|
cumo_cuda_runtime_stage_alloc(&hs->stage, total);
|
|
2066
|
+
hs->range_cost = CUMO_NA_STAGE_COPY_COST + total;
|
|
2067
|
+
hs->row_cost = CUMO_NA_STAGE_COPY_COST;
|
|
2005
2068
|
total = 0;
|
|
2006
2069
|
for (j=0; j<narg; j++) {
|
|
2007
2070
|
if (hs->ptr[j] == NULL) continue;
|
|
2008
2071
|
hs->off[j] = total;
|
|
2009
2072
|
LARG(lp,j).ptr = hs->stage.ptr + total - hs->min[j];
|
|
2010
|
-
|
|
2073
|
+
hs->row_cost += ndloop_stage_row_len(lp, j);
|
|
2011
2074
|
total += (hs->len[j] + 7) & ~(size_t)7;
|
|
2012
2075
|
}
|
|
2013
|
-
|
|
2076
|
+
ndloop_stage_range(lp);
|
|
2077
|
+
hs->seen_epoch = hs->epoch;
|
|
2014
2078
|
}
|
|
2015
2079
|
|
|
2016
2080
|
// Before each row: a row that is only copied as reached, or one that
|
|
2017
|
-
// device memory was written since the copy.
|
|
2081
|
+
// device memory was written since the copy. Once the rows copied since a
|
|
2082
|
+
// write have cost as much as the whole range, and nothing was written while
|
|
2083
|
+
// the block ran last, the whole range is copied again instead.
|
|
2018
2084
|
static inline void
|
|
2019
2085
|
ndloop_stage_before_row(cumo_na_md_loop_t *lp)
|
|
2020
2086
|
{
|
|
2021
2087
|
cumo_na_host_stage_t *hs = lp->hs;
|
|
2088
|
+
size_t now;
|
|
2089
|
+
int quiet;
|
|
2090
|
+
|
|
2022
2091
|
if (hs == NULL) return;
|
|
2023
2092
|
if (hs->direct) cumo_cuda_runtime_sync_if_busy();
|
|
2024
|
-
if (hs->stage.ptr
|
|
2093
|
+
if (hs->stage.ptr == NULL) return;
|
|
2094
|
+
now = cumo_cuda_launch_epoch_now();
|
|
2095
|
+
quiet = hs->seen_epoch == now;
|
|
2096
|
+
hs->seen_epoch = now;
|
|
2097
|
+
if (hs->rows_only) {
|
|
2025
2098
|
ndloop_stage_row(lp);
|
|
2099
|
+
} else if (hs->epoch != now) {
|
|
2100
|
+
if (quiet && hs->range_debt >= hs->range_cost) {
|
|
2101
|
+
ndloop_stage_range(lp);
|
|
2102
|
+
} else {
|
|
2103
|
+
ndloop_stage_row(lp);
|
|
2104
|
+
hs->range_debt += hs->row_cost;
|
|
2105
|
+
}
|
|
2026
2106
|
}
|
|
2027
2107
|
}
|
|
2028
2108
|
|
|
2109
|
+
static inline cumo_na_md_loop_t *
|
|
2110
|
+
ndloop_of_user(cumo_na_loop_t *user)
|
|
2111
|
+
{
|
|
2112
|
+
return (cumo_na_md_loop_t*)((char*)user - offsetof(cumo_na_md_loop_t, user));
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2029
2115
|
// After a yield: the block may have written the element the user function
|
|
2030
2116
|
// reads next, which is copied again on its own so that a block writing at
|
|
2031
2117
|
// every element costs an element, not a row, each time.
|
|
2032
2118
|
void
|
|
2033
2119
|
cumo_na_ndloop_refresh_next(cumo_na_loop_t *user, const void *next, size_t bytes)
|
|
2034
2120
|
{
|
|
2035
|
-
cumo_na_md_loop_t *lp = (
|
|
2121
|
+
cumo_na_md_loop_t *lp = ndloop_of_user(user);
|
|
2036
2122
|
cumo_na_host_stage_t *hs = lp->hs;
|
|
2037
|
-
|
|
2123
|
+
size_t now;
|
|
2124
|
+
int quiet, j;
|
|
2038
2125
|
|
|
2039
2126
|
if (hs == NULL) return;
|
|
2040
2127
|
if (hs->direct) cumo_cuda_runtime_sync_if_busy();
|
|
2041
|
-
if (hs->stage.ptr == NULL
|
|
2128
|
+
if (hs->stage.ptr == NULL) return;
|
|
2129
|
+
now = cumo_cuda_launch_epoch_now();
|
|
2130
|
+
quiet = hs->seen_epoch == now;
|
|
2131
|
+
hs->seen_epoch = now;
|
|
2132
|
+
if (hs->row_epoch == now) return;
|
|
2133
|
+
// Elements copied one at a time since a write have cost as much as the
|
|
2134
|
+
// row, and the block did not write this time: the row is copied again at
|
|
2135
|
+
// once. A block that writes at every element keeps to one.
|
|
2136
|
+
if (quiet && hs->row_debt >= hs->row_cost) {
|
|
2137
|
+
ndloop_stage_row(lp);
|
|
2138
|
+
hs->range_debt += hs->row_cost;
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2042
2141
|
for (j=0; j<hs->narg; j++) {
|
|
2043
2142
|
char *base = hs->stage.ptr + hs->off[j];
|
|
2044
2143
|
if (hs->ptr[j] == NULL) continue;
|
|
2045
2144
|
if ((const char*)next >= base && (const char*)next + bytes <= base + hs->len[j]) {
|
|
2046
2145
|
cumo_cuda_runtime_check_status(cumo_cuda_runtime_memcpy_to_pinned(
|
|
2047
2146
|
(void*)next, hs->ptr[j] + ((const char*)next - LARG(lp,j).ptr), bytes));
|
|
2147
|
+
hs->row_debt += CUMO_NA_STAGE_COPY_COST + bytes;
|
|
2148
|
+
hs->range_debt += CUMO_NA_STAGE_COPY_COST + bytes;
|
|
2048
2149
|
return;
|
|
2049
2150
|
}
|
|
2050
2151
|
}
|
|
2051
2152
|
}
|
|
2052
2153
|
|
|
2154
|
+
int
|
|
2155
|
+
cumo_na_ndloop_writes_existing(cumo_na_loop_t *user)
|
|
2156
|
+
{
|
|
2157
|
+
return ndloop_writes_existing(ndloop_of_user(user));
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
void
|
|
2161
|
+
cumo_na_ndloop_wrote(cumo_na_loop_t *user)
|
|
2162
|
+
{
|
|
2163
|
+
cumo_na_md_loop_t *lp = ndloop_of_user(user);
|
|
2164
|
+
cumo_na_host_stage_t *hs = lp->hs;
|
|
2165
|
+
size_t before = cumo_cuda_runtime_note_device_write();
|
|
2166
|
+
|
|
2167
|
+
if (hs == NULL) return;
|
|
2168
|
+
// The copy holds the element, so the count it adds leaves it current.
|
|
2169
|
+
if (hs->seen_epoch == before) hs->seen_epoch = before + 1;
|
|
2170
|
+
if (hs->row_epoch == before) hs->row_epoch = before + 1;
|
|
2171
|
+
if (hs->epoch == before) hs->epoch = before + 1;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2053
2174
|
static void
|
|
2054
2175
|
ndloop_unstage_host_reads(cumo_na_md_loop_t *lp)
|
|
2055
2176
|
{
|
data/lib/cumo/version.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Cumo
|
|
4
|
-
VERSION = "0.14.
|
|
4
|
+
VERSION = "0.14.1"
|
|
5
5
|
|
|
6
6
|
spec = Gem.loaded_specs["cumo"] if defined?(Gem)
|
|
7
7
|
REVISION = (spec.metadata["cumo_revision"]&.dup&.freeze if spec && spec.full_gem_path == File.expand_path("../..", __dir__))
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cumo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.14.
|
|
4
|
+
version: 0.14.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Naotoshi Seo
|
|
@@ -370,7 +370,7 @@ metadata:
|
|
|
370
370
|
changelog_uri: https://github.com/sonots/cumo/blob/master/CHANGELOG.md
|
|
371
371
|
bug_tracker_uri: https://github.com/sonots/cumo/issues
|
|
372
372
|
rubygems_mfa_required: 'true'
|
|
373
|
-
cumo_revision: v0.
|
|
373
|
+
cumo_revision: v0.14.1
|
|
374
374
|
rdoc_options: []
|
|
375
375
|
require_paths:
|
|
376
376
|
- lib
|