Unnecessary copy using convert.()

I was surprised by this call to convert on a vector generating a copy, when the output type is the same as the input type. Is this desired behaviour?:

julia> A = [1, 2, 3, 4]
4-element Vector{Int64}:
 1
 2
 3
 4

julia> B = convert.(Int64, A)
4-element Vector{Int64}:
 1
 2
 3
 4

julia> A === B
false

julia> @btime convert.(Int64, $A)
  33.073 ns (1 allocation: 96 bytes)
4-element Vector{Int64}:
 1
 2
 3
 4

I’m using Julia 1.8.2

Broadcasting always result in a copy, no matter the function:

julia> A = [1, 2, 3];

julia> B = identity.(A);

julia> A === B
false

However, since broadcasting is element-wise, you should really compare if the elements are === instead, which they are:

julia> A[1] === B[1]
true

The example is then more interesting with a vector of mutables:

julia> A = [[1, 2], [3, 4]];

julia> B = convert.(Vector{Int}, A); # no copy needed

julia> A[1] === B[1]
true

julia> C = convert.(Vector{Float64}, A); # copy needed

julia> A[1] === C[1]
false
2 Likes

I think it would be odd if broadcasting didn’t create a new array. This one is maybe a bit less obvious:

julia> A = [1, 2, 3, 4];

julia> A === Vector(A)
false

This is also why it is frustrating to see people writing x = Vector([1,2,3]). It does needless extra work.

2 Likes

Thanks, both. I’m now wondering how to avoid such a conversion when not required. Must I do an explicit check myself?:

function convertvec(type, vec)
if eltype(vec) == type
    return vec
end
return convert.(type, vec)
end
julia> v = [1,2,3,4];

julia> convert(Vector{Int}, v) === v
true   # no copy was made

julia> convert(Vector{Float64}, v) === v
false   # "copy" was made (i.e. new vector was created with new type)

?

There’s also this convenient syntax for implicit convert:

v2::Vector{Int} = v    # doesn't copy unless necessary
2 Likes

Perfect. What I needed. Thanks!

Maybe I’m misunderstanding, but when I first saw this, I assumed you were looking for reinterpret. This lets you just reinterpret a region of memory as an array of some different type. For example, you could reinterpret v as a Vector{Float64} without making a copy.

In the case that the input and output types are different, I do want the call to do a type conversion, necessitating a copy. Hence why I’m using the function convert.

I still don’t understand what you’re saying. A type conversion does not necessitate a copy, and the title of this thread suggests you don’t want a copy. So what is your actual goal?

For example,

julia> A = [1, 2, 3, 4];
julia> B = reinterpret(Float64, A);
julia> C = reinterpret(Float32, B);
julia> D = reinterpret(ComplexF64, C);

makes no copies. All four arrays point to the same underlying memory; they just tell Julia to interpret that memory in four different ways.

The statements regarding conversion necessitating or not necessitating a copy really depend on what you understand “convert” to mean. I was using it to mean what the method convert does (which does necessitate a copy), and not what the method reinterpret does. And I’m happy to disagree on that meaning; I certainly don’t want to get into an argument over semantics. Fortunately others understood what I meant, and I got the answer I was looking for.

Sorry. I was just trying to help.