Is there a shorter way to write `for m₁ in axes(M, 1), m₂ in axes(M, 2)` in the co

Is there a shorter way to write for m₁ in axes(M, 1), m₂ in axes(M, 2) in the code below? m₁ and m₂ are required.

let M = [1 2 3; 4 5 6]
	res = Any[]
	for m₁ in axes(M, 1), m₂ in axes(M, 2)
		push!(res, (M[m₁, m₂]))
	end
	res
end

Note that the original poster on Slack cannot see your response here on Discourse. Consider transcribing the appropriate answer back to Slack, or pinging the poster here on Discourse so they can follow this thread.
(Original message :slack:) (More Info)

for m in CartesianIndices(M) and then use m[1] and m[2] in your code.

5 Likes

Consider carefully whether you should use Any here, instead of some concrete type. It is likely to hurt performance significantly.

1 Like

Just to be sure, the CartesianIndices provide in this case a shorter syntax but they seem to be 2x less performant in terms of allocations and speed compared to OP’s proposal? Tested it for: M = randn(1_000,1_000)

They should be essentially the same on Julia 1.6 or higher. Which version are you testing?

1 Like

Julia Version 1.6.0-beta1.0 on Win10. Copying test here in case I might be doing something wrong:

M = randn(1_000,1_000)

@btime let  #  74 ms (2979998 allocations: 70 MiB)
	res = Any[]
	for m₂ in axes(M, 2), m₁ in axes(M, 1)
		push!(res, (M[m₁, m₂]))
	end
end

@btime let  # 146 ms (4978020 allocations: 146 MiB)
    res = Any[]
    for m in CartesianIndices(M)
        m₁, m₂ = m[1], m[2]
        push!(res, M[m])
    end
end

It’s typically easier to just benchmark functions rather than lets… but the key is that you need to flag M as an argument to the benchmark kernel with a $ either way. Otherwise, it’s treated as a non-constant global reference, which can have strange performance effects depending upon how inlining happens.

julia> @btime let  #  74 ms (2979998 allocations: 70 MiB)
               res = Any[]
               for m₂ in axes($M, 2), m₁ in axes($M, 1)
                       push!(res, ($M[m₁, m₂]))
               end
       end
  11.836 ms (1000020 allocations: 24.26 MiB)

julia> @btime let  # 146 ms (4978020 allocations: 146 MiB)
           res = Any[]
           for m in CartesianIndices($M)
               m₁, m₂ = m[1], m[2]
               push!(res, $M[m])
           end
       end
  11.574 ms (1000020 allocations: 24.26 MiB)

Of course, now we still have the pessimized storage for the Any[] array, but that just adds ~1ms to each.

5 Likes

@mbauman, thank you for the explanation.
pairs() seems to have similar performance in this case: for (m, Mij) in pairs(M) and then use m[1], m[2] and Mij

1 Like