I need to swap columns or rows of a matrix. I have two ideas:
(1). Create a new matrix and copy cols/rows to the new matrix. We may use @view.
(2). Multiply by a permutation matrix. Of course, we also need to create that permutation matrix (but it’s sparse)
To my understanding, multiplication takes O(n^3) given a square matrix, but not sure how multiplying with a sparse matrix will make a difference or not.
I’m wondering which way is more preferable in terms of performance?
I think it depends also on what you have to do with yr matrix later… With @view you have no allocations but you also have a structure where the data is no longer contigue, so if you have to use it “often” is penalising…
idata is just a scalar value, so doesn’t need any memory allocation, unlike an array. But as I said, you don’t really need to create that variable explicitly (though, of course, x, y = y, x will do that ‘under the hood’.)
This is a good question which I do not completely understand. Because the scope of that scalar is only local to the loop, I guess it is only created in a local memory during computation that does not count as an allocation. I do not know the details of that.
As DNF pointed out, in his version in which this scalar is not created explicitly, it is created anyway. Effectively the @code_typed of the two versions are identical and, if I understand what is there (not sure, though), this part shows that %16 creates a copy of element i, which is then used in the third line of instruction %17.
There’s nothing magic about built-in functions. The suggestions provided above above are just a couple of lines and are basically as good as anything.
There is the internal and undocumented LinearAlgebra.rcswap! that swaps a pair of rows and columns. But I would discourage using that because it is internal (it could change or be removed at any time). Also, it doesn’t perform the originally-requested task of swapping only rows or only columns.
There is also Base.swapcols!, but again this is an internal function that may disappear in the future. But since it is only a 10-line function that doesn’t depend on any other Base internals it is perfectly reasonable to copy-and-paste it into your code as needed:
# swap columns i and j of a, in-place
function swapcols!(a::AbstractMatrix, i, j)
i == j && return
cols = axes(a,2)
@boundscheck i in cols || throw(BoundsError(a, (:,i)))
@boundscheck j in cols || throw(BoundsError(a, (:,j)))
for k in axes(a,1)
@inbounds a[k,i],a[k,j] = a[k,j],a[k,i]
end
end
It’s very similar to the suggestions above, except it adds an @inbounds annotation and consequently requires bounds checks before the loop. You could also do e.g.
if isdefined(Base, :swapcols!)
using Base: swapcols! # assumes the API hasn't changed
else
function swapcols!(...)
...
end
end
Inspecting Base code can be a useful exercise, even if you end up copy-pasting it for safety, since it usually has been reviewed by multiple people and tries extra hard to be robust and reasonably efficient.
Thanks, this is what I do generally, it’s just one of those cases where it feels a bit clunky to have some unrelated utils.jl file in my package to have this sort of simple-but-unrelated-to-the-actual package functionality.
Maybe a bit like the discussion on whether mean should be available when people could just write sum(x)/length(x) - I get these are all language design decisions which can be reasoned about endlessly and I don’t intend to do that here, just wanted to check in.