Skip to content

Commit 85304d4

Browse files
AkhilAkkapellijpsamaroo
authored andcommitted
View functionality with documentation and tests for ChunkSlice in DArray
1 parent 9c7be3f commit 85304d4

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

docs/src/datadeps.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,75 @@ function Dagger.move!(dep_mod::Any, from_space::Dagger.MemorySpace, to_space::Da
220220
return
221221
end
222222
```
223+
224+
### Chunk and DTask slicing with `view`
225+
226+
The `view` function allows you to efficiently create a "view" of a `Chunk` or `DTask` that contains an array. This enables operations on specific parts of your distributed data using standard Julia array slicing, without needing to materialize the entire chunk.
227+
228+
```view(c::Chunk, slices...) & view(c::DTask, slices...)```
229+
230+
These methods create a `view` for a `DArray` `Chunk` object or for a `DTask` that will produce a `DArray` `Chunk`. You specify the desired sub-region using standard Julia array slicing syntax, identical to how you would slice a regular Array.
231+
232+
#### Examples
233+
234+
```julia
235+
julia> A = rand(64, 64)
236+
64×64 Matrix{Float64}:
237+
[...]
238+
239+
julia> DA = DArray(A, Blocks(8,8))
240+
64x64 DMatrix{Float64} with 8x8 partitions of size 8x8:
241+
[...]
242+
243+
julia> chunk = DA.chunks[1,1]
244+
DTask (finished)
245+
246+
julia> view(chunk, :, :) # View the entire 8x8 chunk
247+
ChunkSlice{2}(Dagger.Chunk(...), (Colon(), Colon()))
248+
249+
julia> view(chunk, 1:4, 1:4) # View the top-left 4x4 sub-region of the chunk
250+
ChunkSlice{2}(Dagger.Chunk(...), (1:4, 1:4))
251+
252+
julia> view(chunk, 1, :) # View the first row of the chunk
253+
ChunkSlice{2}(Dagger.Chunk(...), (1, Colon()))
254+
255+
julia> view(chunk, :, 5) # View the fifth column of the chunk
256+
ChunkSlice{2}(Dagger.Chunk(...), (Colon(), 5))
257+
258+
julia> view(chunk, 1:2:7, 2:2:8) # View with stepped ranges
259+
ChunkSlice{2}(Dagger.Chunk(...), (1:2:7, 2:2:8))
260+
```
261+
262+
#### Example Usage: Parallel Row Summation of a DArray using `view`
263+
This example demonstrates how to sum multiple rows of a `DArray` by using `view` to process individual rows within chunks to get Row Sum Vector.
264+
265+
```julia
266+
julia> A = DArray(rand(10,1000), Blocks(2,1000))
267+
10x1000 DMatrix{Float64} with 5x1 partitions of size 2x1000:
268+
[...]
269+
270+
# Helper function to sum a single row and store it in a provided array view
271+
julia> @everywhere function sum_array_row!(row_sum::AbstractArray{Float64}, x::AbstractArray{Float64})
272+
row_sum[1] = sum(x)
273+
end
274+
275+
# Number of rows
276+
julia> nrows = size(A,1)
277+
278+
# Initialize a zero array in the final row sums
279+
julia> row_sums = zeros(nrows)
280+
281+
# Spawn tasks to sum each row in parallel using views
282+
julia> Dagger.spawn_datadeps() do
283+
sz = size(A.chunks,1)
284+
nrows_per_chunk = nrows ÷ sz
285+
for i in 1:sz
286+
for j in 1:nrows_per_chunk
287+
Dagger.@spawn sum_array_row!(Out(view(row_sums, (nrows_per_chunk*(i-1)+j):(nrows_per_chunk*(i-1)+j))), In(Dagger.view(BD.chunks[i,1], j:j, :)))
288+
end
289+
end
290+
291+
# Print the result
292+
julia> println("Row sum Vector: ", row_sums)
293+
Row sum Vector: [499.8765, 500.1234, ..., 499.9876]
294+
```

src/memory-spaces.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ memory_spans(::T) where T<:AbstractAliasing = throw(ArgumentError("Must define `
122122
memory_spans(x) = memory_spans(aliasing(x))
123123
memory_spans(x, T) = memory_spans(aliasing(x, T))
124124

125+
125126
struct AliasingWrapper <: AbstractAliasing
126127
inner::AbstractAliasing
127128
hash::UInt64
@@ -387,3 +388,55 @@ function will_alias(x_span::MemorySpan, y_span::MemorySpan)
387388
y_end = y_span.ptr + y_span.len - 1
388389
return x_span.ptr <= y_end && y_span.ptr <= x_end
389390
end
391+
392+
struct ChunkSlice{N} #<: AbstractAliasing
393+
chunk::Chunk
394+
slices::NTuple{N, Union{Int, AbstractRange{Int}, Colon}}
395+
end
396+
397+
Base.copyto!(dest::ChunkSlice, src::ChunkSlice) = copyto!(view(unwrap(dest.chunk), dest.slices...), view(unwrap(src.chunk), src.slices...))
398+
399+
@inline function view(c::Chunk, slices...)
400+
isa(c.domain, ArrayDomain) || throw(ArgumentError("Chunk must of a DArray (ArrayDomain), got $(typeof(c.domain))"))
401+
nd, sz = ndims(c.domain), size(c.domain)
402+
nd == length(slices) || throw(DimensionMismatch("Expected $nd slices, got $(length(slices))"))
403+
404+
for (i, s) in enumerate(slices)
405+
if s isa Int
406+
1 s sz[i] || throw(ArgumentError("Index $s out of bounds for dimension $i (size $(sz[i]))"))
407+
elseif s isa AbstractRange
408+
isempty(s) && continue
409+
1 first(s) last(s) sz[i] || throw(ArgumentError("Range $s out of bounds for dimension $i (size $(sz[i]))"))
410+
elseif s === Colon()
411+
continue
412+
else
413+
throw(ArgumentError("Invalid slice type $(typeof(s)) at dimension $i, Expected Type of Int, AbstractRange, or Colon"))
414+
end
415+
end
416+
417+
return ChunkSlice(c, slices)
418+
end
419+
420+
view(c::DTask, slices...) = view(fetch(c; raw=true), slices...)
421+
422+
function aliasing(x::ChunkSlice{N}) where N
423+
remotecall_fetch(root_worker_id(x.chunk.processor), x.chunk, x.slices) do x, slices
424+
x = unwrap(x)
425+
v = view(x, slices...)
426+
return aliasing(v)
427+
end
428+
end
429+
430+
function move!(dep_mod, to_space::MemorySpace, from_space::MemorySpace, to::ChunkSlice, from::ChunkSlice)
431+
to_w = root_worker_id(to_space)
432+
remotecall_wait(to_w, dep_mod, to_space, from_space, to, from) do dep_mod, to_space, from_space, to, from
433+
to_raw = unwrap(to.chunk)
434+
from_w = root_worker_id(from_space)
435+
from_raw = to_w == from_w ? unwrap(from.chunk) : remotecall_fetch(unwrap, from_w, from.chunk)
436+
from_view = view(from_raw, from.slices...)
437+
to_view = view(to_raw, to.slices...)
438+
move!(dep_mod, to_space, from_space, to_view, from_view)
439+
end
440+
end
441+
442+
move(from_proc::Processor, to_proc::Processor, slice::ChunkSlice) = view(move(from_proc, to_proc, slice.chunk), slice.slices...)

test/array/allocation.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,44 @@ end
211211
@test A_v == A[1:8, 1:8]
212212
end
213213

214+
@testset "Chunk view of DArray" begin
215+
A = rand(64, 64)
216+
DA = DArray(A, Blocks(8,8))
217+
chunk = DA.chunks[1,1]
218+
219+
@testset "Valid Slices" begin
220+
@test view(chunk, :, :) isa ChunkSlice && view(chunk, 1:8, 1:8) isa ChunkSlice
221+
@test view(chunk, 1:2:7, :) isa ChunkSlice && view(chunk, :, 2:2:8) isa ChunkSlice
222+
@test view(chunk, 1, :) isa ChunkSlice && view(chunk, :, 1) isa ChunkSlice
223+
@test view(chunk, 3:3, 5:5) isa ChunkSlice && view(chunk, 5:7, 1:2:4) isa ChunkSlice
224+
@test view(chunk, 8, 8) isa ChunkSlice
225+
@test view(chunk, 1:0, :) isa ChunkSlice
226+
end
227+
228+
@testset "Dimension Mismatch" begin
229+
@test_throws DimensionMismatch view(chunk, :)
230+
@test_throws DimensionMismatch view(chunk, :, :, :)
231+
end
232+
233+
@testset "Int Slice Out of Bounds" begin
234+
@test_throws ArgumentError view(chunk, 0, :)
235+
@test_throws ArgumentError view(chunk, :, 9)
236+
@test_throws ArgumentError view(chunk, 9, 1)
237+
end
238+
239+
@testset "Range Slice Out of Bounds" begin
240+
@test_throws ArgumentError view(chunk, 0:5, :)
241+
@test_throws ArgumentError view(chunk, 1:8, 5:10)
242+
@test_throws ArgumentError view(chunk, 2:2:10, :)
243+
end
244+
245+
@testset "Invalid Slice Types" begin
246+
@test_throws DimensionMismatch view(chunk, (1:2, :))
247+
@test_throws ArgumentError view(chunk, :, [1, 2])
248+
end
249+
250+
end
251+
214252
@testset "copy/similar" begin
215253
X1 = ones(Blocks(10, 10), 100, 100)
216254
X2 = copy(X1)

0 commit comments

Comments
 (0)