# Why does array packing in Table behave like this?

Consider the following example, which generate two identical arrays. Why sometimes the array is packed and sometimes it doesn't? And why they perform differently?

Here the arrays are packed:

ht = 0.002; Ti = 0.4;
a1 = Table[Sin[x], {x, 0.2, 50., 0.02}]; // AbsoluteTiming
a2 = Table[Sin[x], {x, 0.5 Ti, 125 Ti, 0.05 Ti}]; // AbsoluteTiming
DeveloperPackedArrayQ[a1]
DeveloperPackedArrayQ[a2]
a1 == a2

(*
{0.010927, Null}
{0.335707, Null}
True
True
True
*)


Here one array is packed and the other is not:

a1 = Table[Sin[x], {x, 0.2, 50., 0.2}, {t, 0.002, 25., 0.002}]; // AbsoluteTiming
a2 = Table[Sin[x], {x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 12500 ht, ht}]; // AbsoluteTiming
DeveloperPackedArrayQ[a1]
DeveloperPackedArrayQ[a2]
a1 == a2

(*
{0.371184, Null}
{1.610365, Null}
True
False
True
*)


Since they have huge difference in performance, does that mean we should avoid using variables , instead we should use numbers in the code? Or add Evaluate everywhere we have variables?

-
Have a look here and here. The answers plus comments in these discussions provide a comprehensive view on this issue. –  Leonid Shifrin Mar 28 '13 at 21:26
@LeonidShifrin Thanks for pointing them out, that's very helpful. But according to the accepted answer in the first link, the they should performance the same as long as we use a single index, which doesn't seems to apply here. And some answer claims whether the array is packed is the only reason, which also not seems true here. –  user0501 Mar 28 '13 at 21:51
Just read all the answers and comments there carefully. The issue is that Table can't check that the array will be rectangular, for symbolic array bounds, and so does not pack / auto-compile in this case. –  Leonid Shifrin Mar 28 '13 at 21:57
As the attribute HoldAll is prebuilt into the function and you want to enforce evaluation before the body of Table[] start executing, you have no option other than this. You can also define the {t, ht, 12500 ht, ht} and {x, 0.5 Ti, 125 Ti, 0.5 Ti} OUTSIDE of the Table[] body, and then MAthematica will evaluate the multiplications inside the iterators but this is more cumbersome that the solution I provided. –  Yehuda Mar 30 '13 at 8:12

I think the deeper reason is really contained in the links that Leonid has given, but probably not as to the point for your specific example as possible. The following cures your problem, without the need to avoid variables:

With[{Ti = Ti},
a2 = Table[Sin[x], {x, 0.5 Ti, 125 Ti, 0.05 Ti}]; // AbsoluteTiming
]


Edit: as some comments from people more careful than me have pointed out the above doesn't cure the problem (or more precise in the one-iterator case there isn't a problem to be cured). For the case with two iterators solution along the lines of the above doesn't help, persumably because there are still products in the iterator. The following does work as intended (at least for me):

With[{xmin = 0.5 Ti, xmax = 125 Ti, xstep = 0.5 Ti, ht = ht, tmax = 12500 ht},
a2 = Table[Sin[x], {x, xmin, xmax, xstep}, {t, ht, tmax, ht}]; // AbsoluteTiming
]


Of course this now is much more work for the general case than the methods in Mr. Wizards answer. It also has advantages, though: it does preserve the localization and might be somewhat easier to read and understand for not so advanced users.

What I do is to literally insert the numeric values so that Table now sees that it has to handle constant values and doesn't have to take into account potential complications which could arise from non constant iterators. You might find detailed explanations why that's necessary elsewhere, but sometimes a simple example is easier to understand: Try to predict the result if you use this definition of Ti and you may understand why Table needs to be extra careful whenever it sees symbols in its input:

Module[{x = 10}, Ti := (x++)];
Table[x, {x, 0, 2*Ti, Ti}]


Edit: it is interesting to note that my example is actually identical to the first example in the question, where auto-compilation/packaging did work nevertheless. It looks like for just one iterator the optimizations can be done even when a symbol appears in it, probably because then the generated list always can be assumed to be rectangular (1xn, that is)...

-
That's a good point, +1. –  Leonid Shifrin Mar 28 '13 at 22:11
The With method doesn't work on my system, presumably because you still have a multiplication in the iterator field. –  Mr.Wizard Mar 28 '13 at 22:11
Your example already produces a packed array without With, but does this on your system?: With[{Ti = Ti, ht = ht}, Table[Sin[x], {x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 12500 ht, ht}] // DeveloperPackedArrayQ ] It does not on mine. –  Mr.Wizard Mar 28 '13 at 22:20
@Mr.Wizard It does not indeed, although I don't have the time right now to dig deeper into it. V9, Win 7 64 bit. –  Leonid Shifrin Mar 28 '13 at 22:29
@Mr.Wizard: sorry, I didn't realize that for the first example packing does work even with the variable and probably just was slower by accident (it isn't for me on average). For the other case my first suggestion indeed doesn't work. I'll update. Thanks for pointing out... –  Albert Retey Mar 28 '13 at 22:48
show 1 more comment

Notice the attributes of Table - HoldAll it means that the arguments (the function to be evaluated, AND the iterators) are not evaluated prior to executing the Table[...]. Therefore, the second loop involves computations in the iterators if you change the second Table to

Table[f[t, x], Evaluate[{x, 0.5 Ti, 125 Ti, 0.5 Ti}], Evaluate[{t, ht, 12500 ht, ht}]]; // AbsoluteTiming


it will enforce only a single evaluation of the values and the rest is similar to the iteration of the first loop, so (almost) identical results in timing

a1 = Table[
f[t, x], {x, 0.2, 50., 0.2}, {t, 0.002, 25.,
0.002}]; // AbsoluteTiming
a2 = Table[f[t, x], Evaluate[{x, 0.5 Ti, 125 Ti, 0.5 Ti}],
Evaluate[{t, ht, 12500 ht, ht}]]; // AbsoluteTiming


results with

{0.673810, Null}

{0.671783, Null}

yehuda

-
Thanks for the answer, but do we is there others ways to avoid this other than just use Evaluate everywhere? –  user0501 Mar 27 '13 at 17:31

As described in the Q&A's Leonid referenced(1)(2), Table needs to see explicit iterator values to trigger the internal optimizations. You can pre-evaluate the arguments of Table like this:

packedQ = DeveloperPackedArrayQ;

ht = 0.002; Ti = 0.4;

Table @@ {Sin[x], {x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 12500 ht, ht}} // packedQ


True

Or if Sin[x] would evaluate incorrectly:

Table[Sin[x], ##] &[{x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 12500 ht, ht}] // packedQ


True

Be aware that neither method localizes the iterator variables as Table normally does. You could use Block to do it manually. Alternatively you could inject only the range parameters like this:

x = 5; t = 17; (* global values to demonstrate functioning protection *)

{{0.5 Ti, 125 Ti, 0.5 Ti}, {ht, 12500 ht, ht}} /.
{{s1__}, {s2__}} :> Table[Sin[x], {x, s1}, {t, s2}] // packedQ


True

-
Thanks for the answer, but how do I deal with the situation that there is variables also in the function? For example: n=1.;ht = 0.002; Ti = 0.4; With[{f = Sin[n #1] &}, Table[f[x], ##] &[{x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 1250 ht, ht}]] // packedQ With[{f = Sin[ #1] &}, Table[f[x], ##] &[{x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 1250 ht, ht}]] // packedQ (* False True *) –  user0501 Mar 29 '13 at 16:44
@user6048 I get "True" for both lines of code. If you are certain there are no lingering definitions this may be a difference between versions. "Moving target" questions are discouraged but in this case I think it is appropriate to add the case that does not pack to your question. This way we may get confirmation of your result on other systems and hopefully an elegant solution. At it is you might try a double inject, e.g. With[{n = n}, With[{f = Sin[n #1] &}, Table[f[x], ##] &[{x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 1250 ht, ht}] // packedQ ] ] –  Mr.Wizard Mar 30 '13 at 21:21

[Edit note: This was merged from duplicate question. What are offered below are workarounds for the two-iterator Tables, in response to a comment inquiring about alternatives, so that Table will use internal optimizations. Why there is a need for them is explained in other answers. Here, the solutions address the requirement that the iterator limits have to be evaluated before they are passed to Table in order for packed arrays and the internal optimization to be used.]

## Case: iterator symbols undefined

Here is one option if x and t are undefined.

tab[fn_, rest__] := Table[fn, rest];
SetAttributes[tab, HoldFirst];
a3 = tab[f[t, x], {x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 12500 ht, ht}]; // AbsoluteTiming
(* {0.541578, Null} *)


Another option is to use With:

a4 = With[{iter1 = {x, 0.5 Ti, 125 Ti, 0.5 Ti}, iter2 = {t, ht, 12500 ht, ht}},
Table[f[t, x], iter1, iter2]]; // AbsoluteTiming
(* {0.552246, Null} *)


## Case: blocking symbols that have been defined

If x or t has a defined value, you can use Block:

a5 = Block[{x, t},
With[{iter1 = {x, 0.5 Ti, 125 Ti, 0.5 Ti}, iter2 = {t, ht, 12500 ht, ht}},
Table[f[t, x], iter1, iter2]]
]; // AbsoluteTiming
(* {0.543405, Null} *)


Block can be wrapped around tab, too, but there's a better way.

## Automatically blocking iterator symbols

Block can be incorporated in a custom table function:

SetAttributes[tab2, HoldAll];
tab2[fn_, rest__] :=
With[{iterVars = Cases[Hold[rest], {sym_Symbol, __} :> Hold@sym]},
Block[iterVars, Table[fn, ##] &[rest]] // ReleaseHold];


If we set x equal to something, the table function still generates the desired table.

x = 5;
a6 = tab2[f[t, x], {x, 0.5 Ti, 125 Ti, 0.5 Ti}, {t, ht, 12500 ht, ht}]; // AbsoluteTiming
(* {0.539895, Null} *)


They all give equivalent results:

a1 == a2 == a3 == a4 == a5 == a6
(* True *)


In the fast versions, because the limits are numbers, Table chooses a fast alternative that, as your question shows, uses packed arrays.

-
Michael, I merged the question you answered into this one. Your answer no longer matches the wording exactly. At your preference you may wish to update your answer or the question; if you choose the latter please make it neutral enough that the other answers fit as well. –  Mr.Wizard Mar 30 '13 at 21:14
@Mr.Wizard I revised my answer slightly. Thanks for letting me know. –  Michael E2 Mar 30 '13 at 23:26