Why column major?

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.

1 Like

Just to be clear: what I’m dreaming of is ‘first index major’, but the first index moves along the rows.

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.

1 Like

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)

No problem at all😄

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.’

Is there a package for “first index fastest”?

but in a simple table like this:

John      54kg    1.80m
Paul      80kg    1.70m
Denis     70kg    1.60m

isn’t it natural that running over columns (to compute average properties, for instance) should be the fastest?

2 Likes

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

  1. yes, the language provides a default for Array,

  2. 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).

4 Likes

Regarding your example:

Its transpose is as simple or natural:

        John    Paul    Denis
Weight  54kg    80kg    70kg
Height  1.80m   1.70m   1.60m

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).

2 Likes

Sure, I was thinking of plain numeric arrays, tables would be different.

1 Like

7 posts were split to a new topic: Row vs. column major for arrays of coordinate vectors

A = rand(2,2)' is a row major matrix in natural Julia syntax.

2 Likes

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.

4 Likes

Okay, I’m confused now. What is the fast way to access/create A[i,j,k] in Julia …

  1. with nested for loops on different lines
  2. with nested for loops on one line
  3. 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.)

1 Like

this may help dispel the confusion

2 Likes

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)

7 Likes

these two doesn’t generate different code, just different in style.

2 Likes