A function defined by f[x_]:=<code>
and applied on every element of a list as f /@ testList
seems to take longer than the same anonymous function applied as Function[x,<code>] /@ testList
. Why is that? Is there some extra copying of variables in the first case? Is it not the correct way to define functions?
MWE: Signed volumes
As a MWE, consider a function computing a signed volume defined as follows:
signedVol[lst_] := Det@Table[lst[[i]] - lst[[1]], {i, 2, Length@lst}]
Generate testing data as 100'000 quadruples of points in 3D:
testList = Table[RandomReal[{-5, 5}, {4, 3}], {i, 1, 100000}];
And compare the following two evaluations. In the second, we just replace signedVol
by an anonymous function with identical definition:
time = Now; signedVol /@ testList; Print[Now - time]
time = Now; Function[lst, Det@Table[lst[[i]] - lst[[1]], {i, 2, Length@lst}]] /@ testList; Print[Now - time]
On my computer, I am consistently getting ~0.8s for the former, and ~0.3s for the latter. I also tried for 1'000'000 quadruples, and got a consistent ratio, 9.1s vs 2.9s.
Question
Why is there such a big difference? Is defining a function like this somehow wrong?
I also tried defining signedVol=Function[lst, <code>]
and then applying it, which gave me the same results as applying the anonymous function directly (the latter case). So this seems to be a viable option for fast computations with a predefined function. However, I have never seen this being used, and it feels like a strange construction. Is this something people use?
Version and system
I am using Mathematica 12.3.1 for macOS, and I am on macOS Monterey 12.1
Additional example
I also defined the same computation by my own explicit formula for the 3D case as follows
signedVol3D[lst_] := (
(lst[[2, 1]] - lst[[1, 1]])*(lst[[3, 2]] -
lst[[1, 2]])*(lst[[4, 3]] - lst[[1, 3]])
+ (lst[[4, 1]] - lst[[1, 1]])*(lst[[2, 2]] -
lst[[1, 2]])*(lst[[3, 3]] - lst[[1, 3]])
+ (lst[[3, 1]] - lst[[1, 1]])*(lst[[4, 2]] -
lst[[1, 2]])*(lst[[2, 3]] - lst[[1, 3]])
- (lst[[4, 1]] - lst[[1, 1]])*(lst[[3, 2]] -
lst[[1, 2]])*(lst[[2, 3]] - lst[[1, 3]])
- (lst[[2, 1]] - lst[[1, 1]])*(lst[[4, 2]] -
lst[[1, 2]])*(lst[[3, 3]] - lst[[1, 3]])
- (lst[[3, 1]] - lst[[1, 1]])*(lst[[2, 2]] -
lst[[1, 2]])*(lst[[4, 3]] - lst[[1, 3]])
)
I did the same experiment as with signedVol
and the times are as follows
Code used | Time |
---|---|
signedVol /@ testList |
0.83 seconds |
Function[lst, <code of signedVol>] /@ testList |
0.26 seconds |
signedVol3D /@ testList |
2.30 seconds |
Function[lst, <code of signedVol3D>] /@ testList |
0.05 seconds |
Interestingly, signedVol3D
performs worse than signedVol
, but the anonymous version of signedVol3D
performs better than the anonymous version of signedVol
.
Remark on timing method used
Note that I use the construction time = Now; <code>; Print[Now - time]
instead of Timing
, because Timing
seems to have some unexpected behavior when you run it several times in the same cell -- for example if you run it on the same code multiple times in the same cell, the first evaluation yields faster time than all the following. But there is already a separate question about that.