Edit 2: Thanks to @Rojo for pointing out something I forgot about, which leads me to revise my answer as well as to the following conclusion:
Mathematica can swap elements in an array without copying the array.
What I neglected was $HistoryLength
. Mine is set at $HistoryLength = 5
; the standard default is Infinity
. The issue it causes is that the result of a = Range[100000000];
is stored. When the swap is computed, the value a
has to be copied so that the value in the history remains unchanged (compare with the example from the original answer, b = a; b[[1]] = -1;
down below). If you set $HistoryLength = 0
then the swap occurs in place without copying a
. See, for instance, Old values are not freed/garbage collected when you re-evaluate an assignment. Blank lines indicate separate input cells.
Quit[]
$HistoryLength = 0;
a = Range[100000000];
MaxMemoryUsed[]
(* 823174480 *)
a[[{4, 2}]] = a[[{2, 4}]]; // AbsoluteTiming
(* {0.000011, Null} *)
MaxMemoryUsed[]
(* 823175512 *)
Compare with the evidence I gave in my original answer, during which $HistoryLength
was 5
:
Quit[]
$HistoryLength = 5; (* added *)
a = Range[10^8];
MaxMemoryUsed[]
(* 823176384 *)
a[[{4, 2}]] = a[[{2, 4}]]; (* If $HistoryLength >= 2, then the result of Range[10^8],
two commands ago, has to be preserved in the history *)
MaxMemoryUsed[] (* So the memory usage doubles *)
(* 1623180112 *)
In the other part of my original answer, the problem was to explain why the timing seemed fast on iterated swaps inside a Do
loop. The explanation is the same as above. The intermediate results of each iteration are not stored in the history, so no copying is done. If $HistoryLength = 0
, then all swaps will be fast.
Quit[]
$HistoryLength = 0
SeedRandom[1];
a = Range[10^8];
With[{indices = Range[10^8]},
sw = Table[RandomSample[indices, 2], {1000}]
];
Do[a[[s]] = a[[Reverse@s]], {s, sw}]; // AbsoluteTiming
(* {0.002138, Null} *)
If HistoryLength
is changed to 5
or so, a copy of the initial value of a
will be made on the first swap, but subsequent swaps will be fast. This may be inferred by comparing the Do
with one that does one swap:
Do[a[[s]] = a[[Reverse@s]], {s, sw}]; // AbsoluteTiming
(* {0.430604, Null} *)
For further confirmation, we can see that the bulk of the time corresponds to how long it takes to write 10^8
integers to RAM:
a = Range[10^8]; // AbsoluteTiming
(* {0.371801, Null} *)
A similar comparison to an operation that requires reading and writing: Evaluating b = a
creates a reference but does not copy memory; the subsequent command b[[1]] = -1
forces Mathematica to copy the list (as well as change b[[1]]
).
b = a;
b[[1]] = -1; // AbsoluteTiming
(* {0.437203, Null} *)
Side issue:
The code for the iterated swaps in the original answer was somewhat different. It used a
instead of With
for the indices to generate the list of swaps sw
. Using a
this way causes a
to get copied on the first swap even if $HistoryLength = 0
.
Quit[]
$HistoryLength = 0
SeedRandom[1];
a = Range[10^8];
sw = Table[RandomSample[a, 2], {1000}];
Do[a[[s]] = a[[Reverse@s]], {s, sw}]; // AbsoluteTiming (* first time *)
(* {0.405094, Null} *)
Do[a[[s]] = a[[Reverse@s]], {s, sw}]; // AbsoluteTiming (* second time *)
(* {0.002062, Null} *)
I do not fully understand this. A possible hypothesis is that a reference to the original value of a
is maintained via the RandomSample
code; however, it is not a thoroughly satisfying hypothesis.
Edit 1: Illustration of the multiple swap code.
SeedRandom[1];
a = Range[20]
sw = Table[RandomSample[a, 2], {5}]
(*
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
{{6, 1}, {8, 1}, {3, 4}, {1, 17}, {15, 4}}
*)
Do[a[[s]] = a[[Reverse@s]], {s, sw}];
a
(*
{17, 2, 4, 15, 5, 1, 7, 6, 9, 10, 11, 12, 13, 14, 3, 16, 8, 18, 19, 20}
*)
swap = Function[{list,i,j},list[[{j,i}}]] = list[[i,j]],HoldFirst]
. Example:lst = Range[10]; swap[lst, 3, 7];lst
$\endgroup$swap = Function[{list, i, j}, list[[{j, i}]] = list[[{i, j}]], HoldFirst]
$\endgroup$