Conversation
Store dense matrices as plain row lists for faster entry access.
Some microbenchmarks show the benefit, namely that performance for
basic arithmetic in many cases matches that of ist-of-list
matrices, while IsPlistMatrixRep is basically always slower,
sometimes quite substantially so.
Setup:
R:=GF(257);;n:=20;;
M1:=IdentityMat(n,R);;
M2:=IdentityMatrix(IsPlistMatrixRep,R,n);;
M3:=IdentityMatrix(IsFlatPlistMatrixRep,R,n);;
Addition:
gap> for i in [1..1000] do x:=M1+M1; od; time;
4
gap> for i in [1..1000] do x:=M2+M2; od; time;
12
gap> for i in [1..1000] do x:=M3+M3; od; time;
4
Multiplication:
gap> for i in [1..1000] do x:=M1*M1; od; time;
66
gap> for i in [1..1000] do x:=M2*M2; od; time;
1448
gap> for i in [1..1000] do x:=M3*M3; od; time;
60
Inversion:
gap> for i in [1..1000] do x:=Inverse(M1); od; time;
7
gap> for i in [1..1000] do x:=Inverse(M2); od; time;
83
gap> for i in [1..1000] do x:=Inverse(M3); od; time;
4
Powers:
gap>
gap> for i in [1..1000] do x:=M1^5; od; time;
187
gap> for i in [1..1000] do x:=M2^5; od; time;
4350
gap> for i in [1..1000] do x:=M3^5; od; time;
182
Element access (plist-of-plist benefits from a direct kernel
implementation; we could add that for both IsPlistMatrixRep
and IsFlatPlistMatrixRep, if so desired)
gap> for i in [1..1000000] do x:=M1[1,1]; od; time;
19
gap> for i in [1..1000000] do x:=M2[1,1]; od; time;
78
gap> for i in [1..1000000] do x:=M3[1,1]; od; time;
66
Example of a typical matrix property: IsDiagonalMat
gap> for i in [1..10000] do x:=IsDiagonalMat(M1); od; time;
132
gap> for i in [1..10000] do x:=IsDiagonalMat(M2); od; time;
191
gap> for i in [1..10000] do x:=IsDiagonalMat(M3); od; time;
146
IsOne (surprisingly is slower for IsFlatPlistMatrixRep, I do not
yet know why)
gap> for i in [1..10000] do x:=IsOne(M1); od; time;
144
gap> for i in [1..10000] do x:=IsOne(M2); od; time;
145
gap> for i in [1..10000] do x:=IsOne(M3); od; time;
172
PositionNonZeroInRow is among the core functionality for row reduction:
gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M1,1); od; time;
334
gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M2,1); od; time;
486
gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M3,1); od; time;
376
Co-authored-by: Codex <codex@openai.com>
ThomasBreuer
left a comment
There was a problem hiding this comment.
just a few minor remarks
if we want to describe this representation (in comparison with others) then my impression is:
- a matrix type that can be multiplied with any type of vector object, but formally defines
IsPlistVectorRepas itsCompatibleVectorFilter, - a matrix type that deliberately does not support row access,
- matrix type that supports matrices with zero rows or columns (something which should hold for all matrix types except
IsPlistRep.
for curiosity:
Where does the name IsFlatPlistMatrixRep come from?
GAP has a function Flat, thus one could get the idea that an m times n "flat" matrix is internally represented by a "flat" list of length mn.
| function( row ) | ||
| local copy, i, len; | ||
| len := Length( row ); | ||
| copy := []; |
There was a problem hiding this comment.
Perhaps
| copy := []; | |
| copy := EmptyPlist( len ); |
and then run from 1 to len?
There was a problem hiding this comment.
First did that but then in fact remove this function altogether. Instead three former callers now use ShallowCopy, and one PlainListCopy
| if rows = fail then | ||
| return fail; | ||
| fi; | ||
| return MakeIsFlatPlistMatrixRep( BaseDomain( M ), NrCols( M ), rows, false ); |
There was a problem hiding this comment.
| return MakeIsFlatPlistMatrixRep( BaseDomain( M ), NrCols( M ), rows, false ); | |
| return MakeIsFlatPlistMatrixRep( BaseDomain( M ), rows, rows, false ); |
There was a problem hiding this comment.
I don't understand: rows is list (of rows) here, but the second argument needs to be the number of columnss
There was a problem hiding this comment.
The function computes the inverse of a matrix, and if we arrive at this line then the matrix is square.
There was a problem hiding this comment.
But rows is a list of rows, not an integer
The number of methods for + may change quite a lot
|
The name In PR #6227 I suggested that we could rename However, it might break private user code, so I am not keen on that. I am open to alternatives. Some braingstorming with AI yields these, but perhaps someone has a better idea:
|
|
I like the idea When one wants to "upgrade" code that was written for list-of-lists matrices to matrix objects, switching to |
I have the same question as @ThomasBreuer, I also expected this. Could you possibly indicate what the key difference between matrices in |
|
Concerning the difference between |
|
Everything @ThomasBreuer wrote is true, but the main advantage of this approach is speed: by not having every row be a "rich" and thus expensive object, we avoid a lot overhead in both memory usage and speed. Also, we can directly call into GAP kernel routines for e.g. multiplying two "list of list matrices". As a result, the new type can be almost as fast as the classic "list of list matrices", and in some cases even faster (as it avoids expensive method dispatch overhead) |
IsFlatPlistMatrixRep matrix object typeIsGenericMatrixRep matrix object type
|
I've now renamed this to |
Store dense matrices as plain row lists for faster entry access.
Partially addresses issue #2148 (the unresolved part is that of a "vector object that is not in
IsList)Closes #2973 (an older attempt of something similar)
This PR was created with help of Codex by OpenAI: I planned the work with it, then it did a full first implementation, which was improved by me at first via some rounds of prompts requesting tweaks, then a bunch of manual work to implement details and speedups the AI was not aware.
Since this is fresh, I am sure there are some bugs, and definitely opportunities for things to be improved. And of course plenty of places in the code where using these MatrixObj will blow up because they are not row lists... but that's for future work.
Some microbenchmarks show the benefit, namely that performance for basic arithmetic in many cases matches that of ist-of-list matrices, while IsPlistMatrixRep is basically always slower, sometimes quite substantially so.
I am deliberately doing this over a finite field that is not covered by our compressed matrices, as that is IMHO going to be one of the central use cases in GAP; though for integer/rational matrices, I don't think things will be substantially difference, at least relative to each other. There is no point comparing e.g. over GF(2) to our compressed GF(2) matrices, which of course will be much faster than any of the three implementations being tested here.
Setup:
Addition:
Multiplication:
Inversion:
Powers:
Element access (plist-of-plist benefits from a direct kernel implementation; we could add that for both IsPlistMatrixRep and IsFlatPlistMatrixRep, if so desired)
Example of a typical matrix property: IsDiagonalMat
IsOne (surprisingly is slower for IsFlatPlistMatrixRep, I do not yet know why)
PositionNonZeroInRow is among the core functionality for row reduction: