7
$\begingroup$

I know some function is listable, such that

Sin@{1,2,3,4,5}

gives

{Sin[1], Sin[2], Sin[3], Sin[4], Sin[5]}

By reading some fundamental tutorials, I know the Listable is an efficient way to speed up the calculation so I want all my functions are Listable when applied to the list. Now I want to define this function: $f(x,y,z)=x+y^z$ and use it to calculate a list,for example $1+2^3$ and $4+5^6$.

f = #[[1]] + #[[2]]^#[[3]] &
SetAttributes[f, Listable]
f@{{1, 2, 3}, {4, 5, 6}}

I don't get the result. How can I write a pure function listable using the list as the argument?

$\endgroup$
  • 1
    $\begingroup$ If 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 for f calls on Part. $\endgroup$ – Michael E2 Dec 3 '17 at 4:26
  • $\begingroup$ Is there any options that can control the Listable only applied for the first level? $\endgroup$ – Ice0cean Dec 3 '17 at 4:28
  • 1
    $\begingroup$ Without Listable, if you define f = #1 + #2^#3 & then you can use f @@@ {{1, 2, 3}, {4, 5, 6}} to get your result... Not very helpful for understanding Listable better, though. $\endgroup$ – aardvark2012 Dec 3 '17 at 4:35
  • $\begingroup$ No, I think for controlling the level of listability, you have to use Map or Apply. Note Compile can use listability in the way you desire. But you're limited to what can be compiled. $\endgroup$ – Michael E2 Dec 3 '17 at 4:46
  • $\begingroup$ Have a look at this discussion. In particular, using top-level Listable for speed may lead to the opposite effect, from what you are after. $\endgroup$ – Leonid Shifrin Dec 3 '17 at 5:50
8
$\begingroup$

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. and5^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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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