A long comment...I'm using V13.1, Mac M1 Pro, which might matter...
First, I would point out the "About" section of the profile of user @WReach. Toward the end, the question "Why don't you like to answer questions about Mathematica performance?" is answered. There are many good points, but I'm often rash enough to answer such questions anyway, when I'm not confused.
Second, while @WReach does not mention CPUs (I take the use of "system" to mean Mma), I know just enough about CPUs to get confused and not enough to get unconfused. I know CPUs use heuristics and caches to accelerate some repetitive actions. When does artificially repeating an operation artificially lower the timing? I have no real understanding of this. Can it affect RepeatedTiming[f[x]]
or AbsoluteTiming[Do[f[x], {100}]]
? [Odd performance I discovered about my Mac M1: For large $N$, it can add (resp. multiply) $N$ complex numbers about 15% faster than it can add (resp. multiply) $2N$ real numbers; adding and multiplying take the same time in both cases. Bully for Apple, you might say. CPUs are more sophisticated than I understand.]
Third, to get to the OP's examples, setting mat[[1, 1]]
to 1
takes much, much longer than setting it to 1.
. A packed Real
matrix will be unpacked (i.e. copied into a less efficient form) if you set any of its elements to an Integer
. RepeatedTiming[Do[...]]
hides this because RepeatedTiming
returns a trimmed mean, which discards the long time the first Do
loop trial takes; in subsequent trials, mat
is already unpacked and Do[...]
is efficient.
Fourth, on my machine, unpacking mat
takes 2 or more seconds; copying a packed mat
takes about 0.034s.
mat = RandomReal[{0, 1}, {10000, 10000}];
(foo = mat; foo[[1, 1]] = 1.); // RepeatedTiming
(* copies a packed mat into foo *)
(* {0.034286, Null} *)
(foo = mat; foo[[1, 1]] = 1); // RepeatedTiming
(* unpacks mat into foo *)
(* {2.01679, Null} *)
mat // Developer`FromPackedArray; // RepeatedTiming
(* simply unpacks mat -- why less efficient I don't know *)
(* {2.60058, Null} *)
Fifth, it would be interesting if an actual (complete) application did not have to copy mat
in some form, given that its values are being changed. It's conceivable, and given its size, it's desirable.
Sixth, Does Do[mat[[i, i]] = 1., {i, 1, Length@mat}]
do an in-place update of mat
without copying it? It seems that it should, but on my machine, it seems not if a new mat
is used in each trial:
Table[
mat = RandomReal[{0, 1}, {10000, 10000}];
First@AbsoluteTiming[Do[mat[[i, i]] = 1., {i, 1, 10000}]],
{100}] // Mean
(* 0.0357951 <-- roughly = time to copy mat *)
mat = RandomReal[{0, 1}, {10000, 10000}];
Table[
First@AbsoluteTiming[Do[mat[[i, i]] = 1., {i, 1, 10000}]],
{100}] // Mean
(* 0.00406767 *)
Seventh, using 1.
instead of 1
changes some of the ReplacePart
timings as well. Here's a variant on the OP's that takes just a bit longer than it does to copy mat
:
mat = RandomReal[{0, 1}, {10000, 10000}];
RepeatedTiming[ReplacePart[mat, Table[{i, i} -> 1., {i, Length@mat}]];]
(* {0.0498069, Null} *)
Table[
mat = RandomReal[{0, 1}, {10000, 10000}];
First@AbsoluteTiming[
ReplacePart[mat, Table[{i, i} -> 1., {i, Length@mat}]]],
{30}] // Mean
(* 0.0383822 *)
Eighth, the LinearAlgebra`Private`SetMatrixDiagonal
function takes about 0.048s, or a little slower than Do[]
. Interestingly, it does not matter whether the diagonal is set to ConstantArray[1, Length[mat]]
or to ConstantArray[1., Length[mat]]
. Even with the integer 1
, the diagonal is set to the real 1.
.
Maybe an answer: My guess is that the in-place Do[...]
is fastest and that when mat
is copied. The fastest timing of 0.004s can be achieved if mat
has been "read" into memory, as done by mat + 1.;
below. Of course, that read operation takes time, so it's not that you can get the timing down to 0.004s for free. Note if the line is changed to mat = mat + 1.;
in both places, then the difference in timing increases to 0.049s. If other operations come between the "read" of mat
and the Do
loop, then the timing can increase.
Mean@Table[
mat = RandomReal[{0, 1}, {10000, 10000}];
First@AbsoluteTiming[
mat + 1.;
Do[mat[[i, i]] = 1., {i, 1, 10000}]],
{30}] -
Mean@Table[
mat = RandomReal[{0, 1}, {10000, 10000}];
First@AbsoluteTiming[mat + 1.],
{30}]
(* 0.00451733 *)
Summary: Probably @WReach is right about the difficulty of nailing down precisely what is the best performance. The biggest issue here is 1
vs. 1.
, that is, unpacking vs. keeping mat
packed. The next biggest is probably whether mat
gets copied. That seems tricky. Maybe someone else has a clear view on when.
LinearAlgebra
package no longer exists and I can't useLinearAlgebra`SetMatrixDiagonal
" - it's nowLinearAlgebra`Private`SetMatrixDiagonal[]
$\endgroup$IdentityMatrix
: surely, it is only adding 1 to each element of the diagonal, and not replacing those elements with 1? $\endgroup$Do
approach is fastest because it doesn't involve making a copy. That's going to be hard to beat. $\endgroup$