Since Internal`Bag, Internal`StuffBag and Internal`BagPart can be compiled down, it is a precious source for various applications. There were already many questions why AppendTo is so slow, and which ways exist to make a dynamically grow-able array which is faster. Since inside Compile many tricks can simply not be used, which is for instance the case for Sow and Reap, this is a good alternative.
A fast, compiled version of AppendTo: For a comparison I will use AppendTo directly for an easy loop. Ignore the fact that this would not be necessary here, since we know the number of elements in the result list. In a real application, you maybe wouldn't know this.
appendTo = Compile[{{n, _Integer, 0}},
Module[{i, list = Most[{0}]},
For[i = 1, i <= n, ++i,
AppendTo[list, i];
];
list
]
]
Using Internal`Bag is not as expensive, since in the above code, the list is copied in each iteration. This is not the case for Internal`Bag.
stuffBag = Compile[{{n, _Integer, 0}},
Module[{i, list = Internal`Bag[Most[{0}]]},
For[i = 1, i <= n, ++i,
Internal`StuffBag[list, i];
];
Internal`BagPart[list, All]
]
]
Comparing the run time of both functions uncovers the potential of Internal`Bag:
First[AbsoluteTiming[#[10^5]]] & /@ {appendTo, stuffBag}
(*
{4.298237, 0.003207}
*)
Usage and features
The following information was collected from different sources. Here is an article from Daniel Lichtblau who was kind enough to give some insider information. A question on MathGroup led to a conversation with Oleksandr Rasputinov who knew about the third argument of Internal`BagPart. Various other posts on StackOverflow exist which I will not mention explicitly. I will restrict the following to the usage of Internal`Bag and Compile together. While we have 4 functions (Internal`Bag, Internal`StuffBag, Internal`BagPart, Internal`BagLength), only the first three can be compiled. Therefore, one has to explicitly count the elements which are inserted into the bag if needed (or use Length on All elements).
Internal`Bag[]creates an empty bag of type real. When anIntegeris inserted it is converted toReal.Trueis converted to1.0andFalseto0.0. Other types of bags are possible too. See below.Internal`StuffBag[b, elm]adds an elementelmto the bagb. It is possible to create a bag of bags inside compile. This way it is easy to create a tensor of arbitrary rank.Internal`BagPart[b,i]gives thei-th part of the bagb.Internal`BagPart[b,All]returns a list of all. TheSpanoperator;;can be used too.Internal`BagPartcan have a third argument which is the usedHeadfor the returned expression.- Variables of
Internal`Bag(or general insideCompile) require a hint to the compile for deducing the type. A bag of integers can be declared aslist = Internal`Bag[Most[{0}]] - To my knowledge supported number-types contain
Integer,RealandComplex.
Examples
The important property of the following examples is that they are completely compiled. There is no call to the kernel, and using the Internal`Bag in such a way should most likely speed things up.
The famous sum of Gauss; adding the numbers from 1 to 100. Note that the numbers are not explicitly added. I use the third argument to replace the List head with Plus. The only possible heads inside Compile are Plus and Times and List.
sumToN = Compile[{{n, _Integer, 0}},
Module[{i, list = Internal`Bag[Most[{0}]]},
For[i = 1, i <= n, ++i,
Internal`StuffBag[list, i];
];
Internal`BagPart[list, All, Plus]
]
];
sumToN[100]
Creating a rank-2 tensor by creating the inner bag directly inside the constructor of the outer one:
tensor2 = Compile[{{n, _Integer, 0}, {m, _Integer, 0}},
Module[{list = Internal`Bag[Most[{1}]], i, j},
Table[
Internal`StuffBag[
list,
Internal`Bag[Table[j, {j, m}]]
],
{i, n}];
Table[Internal`BagPart[Internal`BagPart[list, i], All], {i, n}]
]
]
An equivalent function which inserts every number separately
tensor2 = Compile[{{n, _Integer, 0}, {m, _Integer, 0}},
Module[{
list = Internal`Bag[Most[{1}]],
elm = Internal`Bag[Most[{1}]], i, j
},
Table[
elm = Internal`Bag[Most[{1}]];
Table[Internal`StuffBag[elm, j], {j, m}];
Internal`StuffBag[list, elm],
{i, n}];
Table[Internal`BagPart[Internal`BagPart[list, i], All], {i, n}]
]
]
A Position for integer matrices:
position = Compile[{{mat, _Integer, 2}, {elm, _Integer, 0}},
Module[{result = Internal`Bag[Most[{0}]], i, j},
Table[
If[mat[[i, j]] === elm,
Internal`StuffBag[result, Internal`Bag[{i, j}]]
],
{i, Length[mat]}, {j, Length[First[mat]]}];
Table[
Internal`BagPart[pos, {1, 2}],
{pos, Internal`BagPart[result, All]}]
], CompilationTarget -> "C", RuntimeOptions -> "Speed"
]
This last example can easily be used to measure some timings against the kernel function:
times = Table[
Block[{data = RandomInteger[{0, 1}, {n, n}]},
Transpose[{
{n, n},
Sqrt[First[AbsoluteTiming[#[data, 1]]] & /@ {position, Position}]
}]
], {n, 100, 1000, 200}];
ListLinePlot[Transpose[times]]

Open Questions
- Are there simpler/other ways to tell the compiler the type of a local variable? What bothers me here is that this is not really explained in the docs. It is only mentioned shortly how to define (not declare) a tensor. When a user wants to have an empty tensor, it is completely unintuitive that he has to use a trick like
Most[{1}]. Declaring variables would be one of the first things I need, when I would be new toCompile. In this tutorial, I didn't find any hint to this. - Are there further features of
Bagwhich may be important to know in combination withCompile? - The timing function of
positionabove leaks memory. After the run{n, 100, 3000, 200}there is 20GB of memory occupied. I haven't investigated this issue really deeply, but when I don't return the list of positions, the memory seems OK. Actually, the memory for the returned positions should be collected after theBlockfinishes. My system here is Ubuntu 10.04 and Mathematica 8.0.4.