Performance of collecting a zip where one argument to zip has eltype == Union{Nothing, Int}

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)
1 Like