24
$\begingroup$

As I understand it, when using Sow[expr] you throw the expr on some private stack which you can Reap afterwards.

Questions: But what happens if you don't Reap? Does the sowed data remain on this stack? Can this cause issues (memory leaks) if you sow large amounts of data?

$\endgroup$
4
  • 2
    $\begingroup$ Have you tested it yourself? This seems like you could try it out and see. $\endgroup$
    – MarcoB
    Aug 30, 2022 at 14:34
  • $\begingroup$ Try it, it does not increase the memory. $\endgroup$ Aug 30, 2022 at 14:55
  • 2
    $\begingroup$ Somewhat related: mathematica.stackexchange.com/questions/244017/… $\endgroup$
    – Michael E2
    Aug 30, 2022 at 18:28
  • 2
    $\begingroup$ I thought this was going to be a biblical or philosophical question, when it appeared in the HNQ. $\endgroup$
    – Criggie
    Aug 31, 2022 at 19:39

2 Answers 2

28
$\begingroup$

Preamble

This complements the answer of Roman with a few more details.

Reap - Sow implementation is based on an internal object Internal`Bag, which is properly garbage-collectable. Once an expression wrapped in Reap, where Sow has been internally used, goes out of scope, or its evaluation is finished, all these objects are GC-ed and the memory is released.

Also, whenever there is no Reap around Sow, no collection of sown values seems to be at all attempted, and no extra memory is used (i.e., Sow then acts like Identity or #&).

Memory consumption measurements

Reap - Sow measurements

Let us now illustrate that. First, measure memory consumption for 3 cases: simple computation (idle function mapped on a large list), same with Sow in place of an idle function, and same with Reap wrapped around code with Sow inside:

ClearAll[$dataSize, data, wors, rwos, rws, storedBag]

$HistoryLength = 0;

$dataSize = 100000;
data = Range[$dataSize];
wors = MaxMemoryUsed[f /@ data] (* No Reap - Sow at all *)
rwos = MaxMemoryUsed[Sow /@ data] (* Sow without Reap *)
rwos - wors (* Possible extra memory used by Sow without Reap *) 
rws = MaxMemoryUsed[ Reap[Sow /@ data]] (* Sow with surrounding Reap *)
storedBag = rws - rwos (* How much memory take internal structures storing sown results *)

(* 
   7989840
   7990136
   296
   8856288
   866152
*)

The first conclusion we make, is similar to what Roman has stated: there is pretty much no memory wasted when Sow is used without Reap (difference is just a few bytes, 296 here) for even decently sized data.

The last value 866152 is what has been used to internally store sown data.

Internal`Bag[] measurements

Let us now experiment with the Internal`Bag[] structure:

bag = Internal`Bag[]; (* Initialize the bag *)
Do[Internal`StuffBag[bag, i], {i, data}]; (* Fill the bag with the same data *)
ByteCount[bag] (* Unfortunately, ByteCount does not give correct value for bags *)
mu = MemoryInUse[]; (* which is why here we measure the used memory using MemoryInUse *)
Remove[bag]
bagUse = mu - MemoryInUse[]

(*
    33
    866248
*)

The first number shows that ByteCount can not be trusted for bags.

Comparison

The second number we can compare to the value of storedBag variable, which we obtained earlier and in a completely different way:

bagUse - storedBag 

(* 96 *)

I wasn't able to track this remaining difference of 96 bytes down and explain it, but it stays fairly constant when we vary $dataSize within some range, and is a pretty small residual value, compared to the total amount of memory used.

Please note: when running the above code on a fresh kernel, you may need to ignore the first few runs, to start getting stable results similar to those I quoted above. The reason probably has to do with some initialization / autoloading process, although this is just a guess.

What happens if code inside Reap returns early

This has been asked in comments and is a good question. Here is an illustration:

rwsReturn = MaxMemoryUsed[
  Reap[If[# > $dataSize /2, Return[#, Reap], Sow[#]] & /@ data]
]; (* Sow with surrounding Reap, but exiting early *)

storedBagReturn = rwsReturn - rwos (* How much memory takes internal structures storing sown results *)

(* 434160 *)

In this case, I used a 2-argument Return to return early, but the same would've happened had I used Throw / Catch instead. What we see is that memory still has been filled with data up to the point of early return - we get almost exactly half the memory used in the full evaluation case, which is what we would expect here.

Here is a crude way to model how this works:

ClearAll[reap, sow, $storage, $inReap]
$inReap = False;
SetAttributes[{reap}, HoldAll]
reap[code_] := # &@ Block[
  {$inReap = True, $storage}, 
  {code, Internal`BagPart[$storage, All]}
]
sow[arg_] /; !TrueQ[$inReap] := arg;
sow[arg_] := If[ 
  ! ValueQ[$storage],  
  $storage  = Internal`Bag[{arg}]; arg,
  Internal`StuffBag[$storage, arg]; arg
];

The # &@ part in reap implementation is needed if one wants to be able to use 2-arg Return on reap, otherwise one can remove it.

This gives exact same results:

storedBagReturn = MaxMemoryUsed[
  reap[If[# > $dataSize /2, Return[#, reap], sow[#]] & /@ data]
]
storedBagReturn = rwsReturn - rwos (* How much memory takes internal structures storing sown results *)

(* 
  8424264
  434160
*)

So, even though Reap has been interrupted and the sown results have been discarded as well as the result of evaluation, otherwise Reap - Sow work as usual. What matters is that Reap being wrapped around the code creates a dynamic environment in which Sow does collect the data, rather than being idle (which happens when there is no Reap around the code). Whether or not the evaluation is interrupted, does not affect the "collecting" vs "idle" mode for Sow.

Summary

The above analysis indicates that:

  • Sow without surrounding Reap does not use any noticeable extra memory (w.r.t. computations without Sow).
  • Memory consumption of Sow with surrounding Reap is in good agreement with what one would expect based on the behavior of the underlying Internal`Bag[] structure.
  • We have seen that bags are automatically GC-ed once not referenced, which explain why Reap and Sow behave the same.
$\endgroup$
8
  • 2
    $\begingroup$ My inference has been that Reap[] sets up bag(s) to store (appropriately tagged) objects. Sow[] looks for bag(s) in which to stuff an object. If there are no bags appropriate the (tagged) object, Sow[] does nothing. And if nothing, then no memory is used. Perhaps I didn't read your answer carefully enough, but does my description seem an accurate model? -- Further, I'd assume Sow[] stores a pointer and itself has a minimal impact on memory. The real impact comes when Reap constructs the lists of sown objects; then objects might be copied or at least blocked from GC. (+1) $\endgroup$
    – Michael E2
    Aug 30, 2022 at 18:22
  • 1
    $\begingroup$ @MichaelE2 I can't think of anything wrong in your description. Of course, my guess here would be as good as yours, since I didn't see the internal code for Reap/Sow. My understanding is that when Reap is wrapped around, it sets some kind of dynamic Block - like environment, which tells Sow to create bag-based containers for intermediate results as needed (i.e. every time a new tag appears, a new container is created and starts populating). When the code inside Reap is done executing, Reap performs tag filtering and copies collected objects into lists, and then bags are released. $\endgroup$ Aug 30, 2022 at 20:10
  • $\begingroup$ Substituting for data, Developer`FromPackedArray[data], Table[Range[$dataSize], 10] and (a+b)^2000 // Expand yields varying results for me that have me confused. $\endgroup$
    – Michael E2
    Aug 30, 2022 at 22:22
  • 2
    $\begingroup$ Here's the unpacked example: i.stack.imgur.com/6Pa0F.png -- I may have just figured that one out (by giving f a definition): i.stack.imgur.com/mw5y9.png -- Reply to new comment: I noticed the need for some warm-up trials. $\endgroup$
    – Michael E2
    Aug 30, 2022 at 23:09
  • 1
    $\begingroup$ @LeonidShifrin: if I would create a list using Reap[ Do[ Sow[ expr ] ... ] ] and Reap doesn't get evaluated because, e.g. of a throw statement inside expr, would that also count as Reap getting out of scope or its evaluation finishing? $\endgroup$
    – Gert
    Sep 2, 2022 at 9:24
22
$\begingroup$

It looks like there's no memory being used by Sow when there is no encompassing Reap.

With Reap

Exit[]
a = Reap[Do[Sow[i], {i, 2^28}]];
MaxMemoryUsed[]
(*    4532982944    *)

which is a reasonable amount of memory: 4 Gigabytes are required for 2^28 numbers taking 16 bytes each.

Without Reap

Exit[]
Do[Sow[i], {i, 2^28}];
MaxMemoryUsed[]
(*    69922256    *)

which is 70 Megabytes: there seems to be no storage of the sown numbers anywhere, not even a temporary storage.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.