 # Array of Arrays times Array is type unstable

Hi all, I have a code where I need to perform a element by element multiplication of an Array of Arrays `a` of size X with an Array `b` of size X. Note that the inner arrays of `a` are all the same size. In other words, for every inner array of `a`, multiply element-wise by scalar `b[x]`. Using `@code_warntype` shows me that doing `a .* b` is type unstable within a larger code.

If I instead perform a two-level loop to perform the multiplication, then I don’t get a type unstable problem anymore. I can understand that maybe the compiler gets confused by the first way of writing, but then I cannot help it either because, as far as I know, you cannot pre-define the size of the inner arrays of an Array of Arrays, right?

A simple example to illustrate:

``````test1 = Array{Array{Float64,1},1}(undef, 10)
test2 = Array{Float64,1}(undef, 10)
for i = 1:10
test1[i] = rand(100)
test2[i] = rand()
end
function first_test(test1, test2)::Array{Array{Float64,1},1}
return test1 .* test2
end
function second_test(test1, test2)::Array{Array{Float64,1},1}
result = deepcopy(test1) # Have to deepcopy because there is no way to pre-define size of inner arrays
for i = 1:10
for k = 1:100
result[i][k] = test1[i][k] * test2[i]
end
end
return result
end

@code_warntype first_test(test1, test2)
@code_warntype second_test(test1, test2)
``````

Do I have to rely on the two-level loop for these kind of operations? Is there another way to do this?

looks like this is unavoidable, maybe use a matrix for `test1` instead?

1 Like

In this prototype, the dimensions of the inner arrays are the same, but in the future they could vary. Maybe a Dict would be better suited. Thanks!

Benchmarking the code provided, the type unstable version appears to be faster. It’s worth also benchmarking in the real context.

``````julia> @btime first_test(\$test1, \$test2);
1.126 μs (13 allocations: 8.97 KiB)

julia> @btime second_test(\$test1, \$test2);
3.853 μs (13 allocations: 9.27 KiB)
``````

I’m surprised by the type-instability of the broadcasting operation here, but the performance of broadcasting still seems quite good. FWIW, a list comprehension works in this case as well and is type-stable (although it’s slightly slower):

``````julia> function first_test(test1, test2)
return test1 .* test2
end
first_test (generic function with 1 method)

julia> function list_comprehension(test1, test2)
[test1[i] * test2[i] for i in eachindex(test1)]
end
list_comprehension (generic function with 1 method)

julia> using BenchmarkTools

julia> @btime first_test(\$test1, \$test2);
580.734 ns (13 allocations: 8.97 KiB)

julia> @btime list_comprehension(\$test1, \$test2);
604.719 ns (13 allocations: 8.97 KiB)
``````
1 Like

Yeah, I noticed that too, but within the larger function I wrote, the type unstable version is slower, not sure why.

It is indeed, it wasn’t a huge bottleneck in the original code, I was just curious as to whether there was a better way to do this.

``````test1 = Array{Array{Float64,1},1}(undef, 10)
test2 = Array{Float64,1}(undef, 10)
for i = 1:10
test1[i] = rand(100)
test2[i] = rand()
end
function first_test(test1, test2)::Array{Array{Float64,1},1}
return test1 .* test2
end
function second_test(test1, test2)::Array{Array{Float64,1},1}
return [test1[i] * test2[i] for i in eachindex(test1)]
end
``````

In my case (Julia 1.3.1 on Windows 8.1), the list comprehension version is slightly faster:

``````using BenchmarkTools

@btime first_test(\$test1, \$test2);

821.094 ns (13 allocations: 8.97 KiB)

@btime second_test(\$test1, \$test2);

734.008 ns (13 allocations: 8.97 KiB)
``````