Skip to content

Commit bd535d7

Browse files
committed
Implement faster and safer unsafe_copy! in C
* Use `unsafe_copy!` instead of `memcpy` in `vcat` to avoid bypassing the write barrier. * Add test for `copy!` on `#undef` and `unsafe_copy!` with memory alias.
1 parent 3539d8e commit bd535d7

File tree

3 files changed

+98
-20
lines changed

3 files changed

+98
-20
lines changed

base/array.jl

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ function unsafe_copy!{T}(dest::Array{T}, doffs, src::Array{T}, soffs, n)
4747
if isbits(T)
4848
unsafe_copy!(pointer(dest, doffs), pointer(src, soffs), n)
4949
else
50-
for i=0:n-1
51-
@inbounds arrayset(dest, src[i+soffs], i+doffs)
52-
end
50+
ccall(:jl_array_ptr_copy, Void, (Any, Ptr{Void}, Any, Ptr{Void}, Int),
51+
dest, pointer(dest, doffs), src, pointer(src, soffs), n)
5352
end
5453
return dest
5554
end
@@ -615,17 +614,22 @@ function vcat{T}(arrays::Vector{T}...)
615614
end
616615
arr = Array{T}(n)
617616
ptr = pointer(arr)
618-
offset = 0
619617
if isbits(T)
620-
elsz = sizeof(T)
618+
elsz = Core.sizeof(T)
621619
else
622620
elsz = Core.sizeof(Ptr{Void})
623621
end
624622
for a in arrays
625-
nba = length(a)*elsz
626-
ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, UInt),
627-
ptr+offset, a, nba)
628-
offset += nba
623+
na = length(a)
624+
nba = na * elsz
625+
if isbits(T)
626+
ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, UInt),
627+
ptr, a, nba)
628+
else
629+
ccall(:jl_array_ptr_copy, Void, (Any, Ptr{Void}, Any, Ptr{Void}, Int),
630+
arr, ptr, a, pointer(a), na)
631+
end
632+
ptr += nba
629633
end
630634
return arr
631635
end

src/array.c

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ int jl_array_store_unboxed(jl_value_t *el_type)
3232
return store_unboxed(el_type);
3333
}
3434

35+
STATIC_INLINE jl_value_t *jl_array_owner(jl_array_t *a)
36+
{
37+
if (a->flags.how == 3) {
38+
a = (jl_array_t*)jl_array_data_owner(a);
39+
assert(a->flags.how != 3);
40+
}
41+
return (jl_value_t*)a;
42+
}
43+
3544
#if defined(_P64) && defined(UINT128MAX)
3645
typedef __uint128_t wideint_t;
3746
#else
@@ -190,14 +199,9 @@ JL_DLLEXPORT jl_array_t *jl_reshape_array(jl_value_t *atype, jl_array_t *data,
190199
a->flags.ptrarray = 1;
191200
}
192201

193-
jl_array_t *owner = data;
194202
// if data is itself a shared wrapper,
195203
// owner should point back to the original array
196-
if (owner->flags.how == 3) {
197-
owner = (jl_array_t*)jl_array_data_owner(owner);
198-
}
199-
assert(owner->flags.how != 3);
200-
jl_array_data_owner(a) = (jl_value_t*)owner;
204+
jl_array_data_owner(a) = jl_array_owner(data);
201205

202206
a->flags.how = 3;
203207
a->data = data->data;
@@ -526,11 +530,7 @@ JL_DLLEXPORT void jl_arrayset(jl_array_t *a, jl_value_t *rhs, size_t i)
526530
}
527531
else {
528532
((jl_value_t**)a->data)[i] = rhs;
529-
jl_value_t *owner = (jl_value_t*)a;
530-
if (a->flags.how == 3) {
531-
owner = jl_array_data_owner(a);
532-
}
533-
jl_gc_wb(owner, rhs);
533+
jl_gc_wb(jl_array_owner(a), rhs);
534534
}
535535
}
536536

@@ -921,6 +921,67 @@ JL_DLLEXPORT jl_array_t *jl_array_copy(jl_array_t *ary)
921921
return new_ary;
922922
}
923923

924+
// Copy element by element until we hit a young object, at which point
925+
// we can continue using `memmove`.
926+
static NOINLINE ssize_t jl_array_ptr_copy_forward(jl_value_t *owner,
927+
void **src_p, void **dest_p,
928+
ssize_t n)
929+
{
930+
for (ssize_t i = 0; i < n; i++) {
931+
void *val = src_p[i];
932+
dest_p[i] = val;
933+
// `val` is young or old-unmarked
934+
if (val && !(jl_astaggedvalue(val)->bits.gc & GC_MARKED)) {
935+
jl_gc_queue_root(owner);
936+
return i;
937+
}
938+
}
939+
return n;
940+
}
941+
942+
static NOINLINE ssize_t jl_array_ptr_copy_backward(jl_value_t *owner,
943+
void **src_p, void **dest_p,
944+
ssize_t n)
945+
{
946+
for (ssize_t i = 0; i < n; i++) {
947+
void *val = src_p[n - i - 1];
948+
dest_p[n - i - 1] = val;
949+
// `val` is young or old-unmarked
950+
if (val && !(jl_astaggedvalue(val)->bits.gc & GC_MARKED)) {
951+
jl_gc_queue_root(owner);
952+
return i;
953+
}
954+
}
955+
return n;
956+
}
957+
958+
// Unsafe, assume inbounds and that dest and src have the same eltype
959+
// `doffs` and `soffs` are zero based.
960+
JL_DLLEXPORT void jl_array_ptr_copy(jl_array_t *dest, void **dest_p,
961+
jl_array_t *src, void **src_p, ssize_t n)
962+
{
963+
assert(dest->flags.ptrarray && src->flags.ptrarray);
964+
jl_value_t *owner = jl_array_owner(dest);
965+
// Destination is old and doesn't refer any young object
966+
if (__unlikely(jl_astaggedvalue(owner)->bits.gc == GC_OLD_MARKED)) {
967+
jl_value_t *src_owner = jl_array_owner(src);
968+
// Source is young or might refer young objects
969+
if (!(jl_astaggedvalue(src_owner)->bits.gc & GC_OLD)) {
970+
ssize_t done;
971+
if (dest_p < src_p || desc_p > src_p + n) {
972+
done = jl_array_ptr_copy_forward(owner, src_p, dest_p, n);
973+
dest_p += done;
974+
src_p += done;
975+
}
976+
else {
977+
done = jl_array_ptr_copy_backward(owner, src_p, dest_p, n);
978+
}
979+
n -= done;
980+
}
981+
}
982+
memmove(dest_p, src_p, n * sizeof(void*));
983+
}
984+
924985
JL_DLLEXPORT void jl_array_ptr_1d_push(jl_array_t *a, jl_value_t *item)
925986
{
926987
assert(jl_typeis(a, jl_array_any_type));

test/core.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3984,6 +3984,19 @@ g = reinterpret(UInt8, UInt16[0x1, 0x2])
39843984
@test check_nul(copy(g))
39853985
end
39863986

3987+
# Copy of `#undef`
3988+
copy!(Vector{Any}(10), Vector{Any}(10))
3989+
function test_copy_alias{T}(::Type{T})
3990+
ary = T[1:100;]
3991+
unsafe_copy!(ary, 1, ary, 11, 90)
3992+
@test ary == [11:100; 91:100]
3993+
ary = T[1:100;]
3994+
unsafe_copy!(ary, 11, ary, 1, 90)
3995+
@test ary == [1:10; 1:90]
3996+
end
3997+
test_copy_alias(Int)
3998+
test_copy_alias(Any)
3999+
39874000
# issue #15370
39884001
@test isdefined(Core, :Box)
39894002
@test !isdefined(Base, :Box)

0 commit comments

Comments
 (0)