I don’t think this is the case. The memory layout of the “data” portion of the array is identical to that of isbitstype as long as the element type is of small union:
julia> x = Union{Int,Nothing}[1, 2, 3]
3-element Array{Union{Nothing, Int64},1}:
1
2
3
julia> y = unsafe_wrap(Array, Ptr{Int}(pointer(x)), size(x))
3-element Array{Int64,1}:
1
2
3
Besides, it doesn’t explain why collect(Tuple{Char, Int64}, zip($A, $y)) is also slow.
I think the problem is the allocation of intermediate tuples due to union splitting not working well with tuple-of-unions. With manual implementations
function collect_int_char1(xs)
ys = Vector{Tuple{Char,Int}}(undef, length(xs))
a, b = xs.is
for i in eachindex(ys, xs.is...)
x1 = @inbounds a[i]
x2 = @inbounds b[i]
if x1 isa Char
if x2 isa Int
@inbounds ys[i] = (x1, x2)
end
end
end
return ys
end
function collect_int_char2(xs)
ys = Vector{Tuple{Char,Int}}(undef, length(xs))
a, b = xs.is
for i in eachindex(ys, xs.is...)
x1 = @inbounds a[i]
x2 = @inbounds b[i]
x = (x1, x2)
if x isa Tuple{Char,Int}
@inbounds ys[i] = x
end
end
return ys
end
I get
julia> @btime collect_int_char1(zip($A, $y));
11.582 μs (4 allocations: 156.38 KiB)
julia> @btime collect_int_char2(zip($A, $y));
591.180 μs (10004 allocations: 468.88 KiB)
while
julia> @btime collect(Tuple{Char, Int64}, zip($A, $y));
795.722 μs (19493 allocations: 617.14 KiB)
julia> @btime collect(zip($A, $y_pure));
18.759 μs (4 allocations: 156.38 KiB)
So, type-asserting before creating a tuple helps:
julia> @btime collect(zip($A, (x::Int for x in $y)));
23.899 μs (5 allocations: 156.39 KiB)