Exactly. Linear algebra and text chose different conventions, and we’re paying the price for that. Either we change how linear algebra notation works or we start reading column-major like Traditional Chinese (except with columns left to right). I didn’t know images were row-major, but I guess it makes sense if they needed to be used as input for dot-matrix printers.
Simple, create a package called “Row Major Linear Algebra”. Problem solved.
When you read a book, you read a word, then the next line, then the next page, then the next book.
With your scheme there is also no fixed meaning to the dimensions. If you start with a book, you find the specific word along the third dimension. If you start with a library, it’s the fourth (or maybe you count sections, so fifth?). If it’s a multiverse, it’s, I dunno.
There’s no fixed point when you start from the outside going in, the dimensions are just endlessly pushed outwards in a mindwarping way. Instead, start on the inside, and build out. Later dimensions are further out, referring to larger entities.
I actually meant to reply to yha but I mis-clicked your reply button. We’re on the same page here, I totally agree with everything you’ve said. I can’t change who I reply things to, but I can edit my previous comment to emphasize that I agree with you. Sorry, what a mess.
Interesting. I see your point now, though this never bothered me at all when using MSB-first indexing in numpy. Possibly because I tend to think in abstract terms of “k-th dimension” rather than rows/columns whenever the arrays are more than 2d.
(but if I were dictator of Julia, I’d still choose to copy numpy rather than MATLAB on this issue)
I guess there’s no way to be truly consistent, sometimes it makes sense to start on the inside and build outwards, sometimes you drill down. But I really have a problem with ‘fastest index moves outwards’. I like ‘first index is fastest, period.’
What is “natural” in these discussions may be very application-dependent. Some applications benefit from either choice, or possible hybrids (eg block storage).
The Julian answer to “row or column major” is that
yes, the language provides a default for Array,
but the important thing is that there is an abstract interface for most operations (indexing, iteration, etc) so that users are free to define their own container types that does what they please.
(Also, a quick & dirty row major array is just a PermutedDimsArray away).
Think about the weight and height of a population and put that in that table (or the grades of an exam). The point is that the number of values is much greater than the number of categories.
But of course this is case dependent an application dependent. My note was only because sometimes seeing one example where the option makes sense helps us remember why it is as it is (even if it could be otherwise because of other cases).
Let me add my my 2¢ too. I confess the issue and the arguments initially made my brain fry, it is indeed quite interesting. But this is how I view it now.
With the notational convention A[i,j] we tend to call i the row index and j the column index. Taking into consideration that Julia is column-major, it seems weird (or at least inconsistent or inconvenient) to some that the first index corresponds to a row, while it is only the second index that corresponds to a column. Perceived weirdness of this convention is even magnified when another dimension is added and we have A[i,j,k]. This may now look inconsistent because asymmetric. On one hand we say that the prioritized (fast) dimension is columns, then rows and then… how shall we call it… say, floors (with a reference to a virtual building where we put one matrix on top of another). But on the other hand we order the indices as rows, columns, floor, as in A[i,j,k]. Weird.
Or not? In fact, I no longer think so. I find it quite consistent. But I think it helps to view the i as an index with which we move along the first dimension (that is, along a given column) rather than just the index of a row. Similarly j is used to move along a given row. And k is used to move… welll, vertically (again, with the reference to a virtual building). The perceived asymetry then disappears, doesn’t it? It is not rows → columns → floors but rather indexing/moving along the given column → along the row → along… well… simply moving vertically. It seems to me perfectly consistent with column-major format.
Perhaps my displayed troubles to find a descriptive word for the third direction can partially explain where the confusion comes from. k is an index of a floor and by increasing k we are moving along… along some “vertical”. Similarly i is an index of a row but by increasing it we are moving along a column.
Okay, I’m confused now. What is the fast way to access/create A[i,j,k] in Julia …
with nested for loops on different lines
with nested for loops on one line
with an array comprehension
… and how do you reason through their order?
I understand what column major storage means, but not necessarily the syntax for accessing it.
(Btw, I looked for this information in the Performance and Multi-dimensional Arrays sections of the docs but couldn’t find it.)
Each one remembers this as he/she wants. But one way it to remember that: run first over the first index:
julia> function update_fast!(A)
for k in axes(A,3)
for j in axes(A,2)
for i in axes(A,1) # <--- running first
A[i,j,k] = A[i,j,k] - 1.0
end
end
end
end
update_fast! (generic function with 1 method)
julia> function update_slow!(A)
for i in axes(A,1)
for j in axes(A,2)
for k in axes(A,3) # <--- running first
A[i,j,k] = A[i,j,k] - 1.0
end
end
end
end
update_slow! (generic function with 1 method)
julia> A = rand(50,50,50);
julia> using BenchmarkTools
julia> @btime update_fast!($A);
22.939 μs (0 allocations: 0 bytes)
julia> @btime update_slow!($A);
138.349 μs (0 allocations: 0 bytes)