I've read and been told way too many times that functional algorithms and data structures have an obligatory O(log(N))
slowdown in respect to their procedural (for-loop/array-based) counterparts. But, after thinking about it, I don't see how that could be true. Mind this for loop
int arbitrary_sum(int* arr, int size, int n){
int sum = 0;
for (int i=0; i<n; ++i)
sum += arr[(int)(random()*size)];
return sum;
}
That sums N
random elements of an array. You'd saiy this is O(N)
, but that is taking in account array indexing is O(1)
, which can't be true. For simplicity, assume memory cells are stored linearly. Since that chip must occupy a linear amount of space, and since a signal requires a linear amount of time to travel given space, then it is obvious an indexing operation takes at least O(S)
time to complete. That is the time a signal takes to go from the CPU to the memory cell and come back. This means that the complexity of a for-loop is O(N*S), where N
is the number of interactions and S is the size of the state that is visible on that for-loop
. This way, the textbook int sum(int* array, int size)
function, for example, can't be O(N)
- it is, actually, O(N^2)
, for any reasonable implementation of for
, as far as physical laws apply. Using layers of caches we are able to shrink the initial S
factor and amend this effects, but that has a runtime cost and doesn't change the asymptotics. Now, lets look at the functional counterpart. The most obvious way to sum a container of numbers functionally is folding over a list. For simplicity, lets examine the sum of 0
to 4
using church lists:
((λc.(λn.(c 0 (c 1 (c 2 (c 3 (c 4 n))))))) + 0)
We can optimally evaluate a λ program is using an interaction net, so, lets encode that program as one:
Now, let me propose a new model of computer. Instead of separating the CPU and the memory, we instead store a simplistic computing unit together with each memory cell, each one capable of sending/receiving signals and processing local reductions. On this system, we can reduce the ((λ ...list...) +)
abstraction by sending parallel signals from the topmost λ
node to each corresponding application. We can also reduce the outermost abstraction by sending 0
to the last application of the list. All that takes no more than 2 runs through the list, and the result is:
From here on, it is clear that the rest of the computation will take a linear amount of time to complete. After all, each redex is local, and the redexes are reduced one by one, producing the result from the tail of the list up to its head.
Under this point of view, it seems to me that functional programs are actually asymptotically faster than imperative counterparts, so, why is the opposite considered the truth?