In general, as Henrik and I note in the comments, Mathematica makes sure only to copy data when it has undergone some sort of change internally. An easy way to see this is to set a flag like Valid and see when it disappears:
myBigData = RandomReal[{}, {500, 800}];
myBigData // System`Private`SetValid;
amIDifferentNowHeld[Hold[data_]] :=
! System`Private`ValidQ[data]
amIDifferentNow[data_] :=
! System`Private`ValidQ[data]
amIDifferentNow[myBigData]
False
amIDifferentNowHeld[Hold[myBigData]]
False
trueCopy[data_] :=
BinaryDeserialize[BinarySerialize[data]];
amIDifferentNow[trueCopy[myBigData]]
True
So it seems pretty clear to me that we were working with the same object in both the unheld and held cases and this is why you can pass big arrays and stuff through a program and not need to worry about pass-by-value or pass-by-reference. It's really all pass-by-reference anyway with a pointer to some kind of internal Expression object.
Now, where Hold does buy you something is in data-safety. Consider this kind of naive data cleaning you might think to do (basically copied from some old code I wrote). I wanted to canonicalize some options and merge with some defaults so that they were a nice single Association I could store and query later and feed through my program. Here's how that looked:
$defaults = {
{
"GridOptions" -> {"Domain" -> {{-5, 5}, {-5, 5}}, "Points" -> {60, 60}},
"PotentialEnergyOptions" -> {"PotentialFunction" -> (1/2 #^2 &)},
"KineticEnergyOptions" -> {"Masses" -> {1, 1}, "HBars" -> {1, 1}}
}
};
Options[doADVR] = {
"KineticEnergyMatrix" -> None,
"PotentialEnergyMatrix" -> None
};
getWfs[ops : OptionsPattern[]] :=
Module[{dom, pts, potF, m, hb, ke, pe, opts},
opts = Merge[
{
Normal@{ops},
$defaults
},
First
];
(* do the real calculation, usually, but that's not the point here *)
opts
]
And here's what happens when you feed in a SparseArray for one of the "*Matrix" arguments:
ke = $H4DDVR["KineticEnergy", "Points" -> {30, 30}];
ke // Head
ke // Dimensions
SparseArray
{900, 900}
dvrOpts = getWfs["KineticEnergyMatrix" -> ke];
dvrOpts["KineticEnergyMatrix"] // Head
dvrOpts["KineticEnergyMatrix"] // Dimensions
List
{900, 900}
We can see that Normal converted it to a List! As these things grow in size the penalty for that gets more and more dramatic.
And Mathematica provides lots of ways for you to shoot yourself in the foot like this: Normal recurses into data structures, ReplaceAll digs into even PackedArray structures, etc. and those are the well designed functions. Too much of the system tries to be "clever" or "helpful" and in doing so makes it really easy to wreck your data by accident.
Obviously Hold has its core usage in clever destructing-based meta-programming, but in terms of performance a big place Hold can help you out is in making sure your data flows through a program unadulterated, e.g.:
dvrOpts["KineticEnergyMatrix"] // Head
ReleaseHold@dvrOpts["KineticEnergyMatrix"] // Head
ReleaseHold@dvrOpts["KineticEnergyMatrix"] // Dimensions
Hold
SparseArray
{900, 900}
I'd say the place where I actually use Hold the most is when doing crazy things with like Block to ensure no evaluation, though, which often look like:
doASymbolicThing~SetAttributes~HoldAll
$defaultSymbolList = Thread@Hold[{x, y, z, a, b, c}];
doASymbolicThing[symbolicExpr_,
symbols1 : {__Symbol},
symbol2_Symbol
] :=
Replace[
Thread[
DeleteDuplicates@
Join[Thread[Hold[symbols1]], $defaultSymbolList, {Hold[symbol2]}],
Hold
],
Hold[symList : {__Symbol}] :>
Block[symList,
processSymbolicExprSafely[symbolicExpr, symList]
]
]
data, I don't see any change in the timings. I've also tried to unpack the array and to use symbols with downvalues, without any clear influence on the result. – Lukas Lang Apr 10 '19 at 15:12Functionsimply being well-optimized for this sort of thing? Also, you can get a bit of a speedup if you useSlotinstead (Function[Null, #[[1]], HoldAllComplete]) – Sjoerd Smit Apr 10 '19 at 15:22Holdattribute prevents mathematica from copying data, which can be important when working with large datasets when there is a risk of running out of RAM or hitting the swap memory limit. However, I found it difficult to find good examples of this. – Sjoerd Smit Apr 10 '19 at 15:44HoldAllComplete[]has an example using the Hofstadter-Conway sequence which shows why it is sometimes faster to use it. – Somos Apr 10 '19 at 15:50Hold*will help a lot regardless is if you do lots of manipulations. Think passing a huge area throughOptionsPatternandMerge-ing the results and things. There passing in aHold[localVar$nn]has in the past bought me huge speed ups. – b3m2a1 Apr 13 '19 at 06:29Hold-Attributes give you the edge only if you want to modify data in place. Then it will safe you a couple of copy operations and, probably more importantly, a lot of memory allocation and deallocation under the hood.Hold-attributes also disable pattern matching which can also be a reason why, at timesHold-Attributes might result in faster code: They enforce you to aviod complicated patterns in a function definition. – Henrik Schumacher Oct 19 '19 at 12:32data = ...changed todata := .... For immediate definitions with inert values, the computational time of the extra evaluations is vanishing small. – WReach Oct 25 '19 at 15:41