I don't think making functions Listable
is a good way to speed up computations, and in fact it can cause a slow down as discussed in this answer by @LeonidShifrin to the question Two ways to map a function on a list: Which one is faster?. Let's take your example. Here is how you can make f
Listable
:
SetAttributes[f, Listable];
f[x_, y_, z_] := x + y^z
For your example input:
f[{1, 4}, {2, 5}, {3, 6}]
{9, 15629}
This example is simple enough that a Trace
might be helpful in understanding the process:
Trace[f[{1, 4}, {2, 5}, {3, 6}]]
{f[{1,4},{2,5},{3,6}],{f[1,2,3],f[4,5,6]},{f[1,2,3],1+2^3,{2^3,8},1+8,9},{f[4,5,6],4+5^6,{5^6,15625},4+15625,15629},{9,15629}}
The Listable
attribute causes f[{1, 4}, {2, 5}, {3, 6}]
to evaluate to {f[1, 2, 3], f[4, 5, 6]}
. But, this evaluation is done in top-level code, so it is slow. Also, none of the arithmetic operations are vectorized (I will explain this later). Now, let's compare this with the same function with Listable
set to false:
ClearAttributes[g, Listable]
g[x_, y_, z_] := x + y^z
Here is a timing comparison between f
and g
:
{arg1, arg2, arg3} = RandomReal[10, {3, 10^6}];
r1 = f[arg1, arg2, arg3]; //AbsoluteTiming
r2 = g[arg1, arg2, arg3]; //AbsoluteTiming
r1 === r2
{1.24893, Null}
{0.00895, Null}
True
We see that g
is more than 2 orders of magnitude faster. This is because g
only has one top level evaluation before doing the vectorized Plus
and Power
evaluations (by vectorized I mean their listability is built-in to the kernel when dealing with packed arrays). Again, a Trace
might be helpful here:
Trace @ g[
Developer`ToPackedArray@{1, 4},
Developer`ToPackedArray@{2, 5},
Developer`ToPackedArray@{3, 6}
]
{{Developer`ToPackedArray[{1,4}],{1,4}},{Developer`ToPackedArray[{2,5}],{2,5}},{Developer`ToPackedArray[{3,6}],{3,6}},g[{1,4},{2,5},{3,6}],{1,4}+{2,5}^{3,6},{{2,5}^{3,6},{8,15625}},{1,4}+{8,15625},{9,15629}}
The arguments to g
need to be packed in order for the Plus
/Power
evaluations to be vectorized (fast). Notice how {2, 5}^{3,6}
evaluates directly to {8, 15625}
. That is, there is no intermediate step of {2, 5}^{3, 6}
evaluating to {2^3, 5^6}
and then evaluating 2^3. and
5^6`. This is why vectorized arithmetic is so much faster than non-vectorized arithmetic. If we didn't use packed arrays:
Trace @ g[
{1, 4},
{2, 5},
{3, 6}
]
{g[{1,4},{2,5},{3,6}],{1,4}+{2,5}^{3,6},{{2,5}^{3,6},{2^3,5^6},{2^3,8},{5^6,15625},{8,15625}},{1,4}+{8,15625},{1+8,4+15625},{1+8,9},{4+15625,15629},{9,15629}}
Notice how this time there are many extra arithmetic operations when compared to the vectorized (packed) version above. The moral of the story is that giving a function the Listable
attribute can be useful for simplifying code, but it is rarely beneficial for improving speed.
f
were listable, then the output would be the result of{{f[1], f[2], f[3]}, {f[4], f[5], f[6]}}
, which would result in errors, since the code forf
calls onPart
. $\endgroup$Listable
only applied for the first level? $\endgroup$Listable
, if you definef = #1 + #2^#3 &
then you can usef @@@ {{1, 2, 3}, {4, 5, 6}}
to get your result... Not very helpful for understandingListable
better, though. $\endgroup$Map
orApply
. NoteCompile
can use listability in the way you desire. But you're limited to what can be compiled. $\endgroup$Listable
for speed may lead to the opposite effect, from what you are after. $\endgroup$